package com.ctshk.rpc.tour.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ctshk.common.constant.ElasticSearchConstant;
import com.ctshk.common.constant.RedisConstants;
import com.ctshk.common.constant.StringPool;
import com.ctshk.common.dto.Result;
import com.ctshk.common.elasticsearch.config.ElasticSearchConfiguration;
import com.ctshk.common.enums.*;
import com.ctshk.common.enums.Currency;
import com.ctshk.common.exception.BusinessException;
import com.ctshk.common.model.TokenUser;
import com.ctshk.common.service.support.BaseService;
import com.ctshk.common.utils.EntityUtil;
import com.ctshk.rpc.cms.dto.*;
import com.ctshk.rpc.cms.req.BOQueryReq;
import com.ctshk.rpc.cms.req.BannerPositionByTypeReq;
import com.ctshk.rpc.cms.service.*;
import com.ctshk.rpc.order.tour.code.CostSharingType;
import com.ctshk.rpc.order.tour.code.IsOccupyBedCode;
import com.ctshk.rpc.order.tour.code.PlusTypeCode;
import com.ctshk.rpc.order.tour.dto.SeriesOrderDTO;
import com.ctshk.rpc.order.tour.service.ISeriesOrderService;
import com.ctshk.rpc.system.dto.*;
import com.ctshk.rpc.system.dto.AreaDTO;
import com.ctshk.rpc.system.req.AreaQueryReq;
import com.ctshk.rpc.system.service.IMainDataTourGroupAreaService;
import com.ctshk.rpc.system.service.IMainDataTourGroupCodeService;
import com.ctshk.rpc.system.service.IMainDataTourGroupService;
import com.ctshk.rpc.system.service.ISysAreaService;
import com.ctshk.rpc.tour.dto.*;
import com.ctshk.rpc.tour.dto.cache.*;
import com.ctshk.rpc.tour.dto.schedule.SeriesTripGroupDTO;
import com.ctshk.rpc.tour.enmus.Constants;
import com.ctshk.rpc.tour.enmus.ScheduleStatusCode;
import com.ctshk.rpc.tour.enmus.TripStatusCode;
import com.ctshk.rpc.tour.entity.*;
import com.ctshk.rpc.tour.entity.es.TripCache;
import com.ctshk.rpc.tour.mapper.*;
import com.ctshk.rpc.tour.req.BranchQueryReq;
import com.ctshk.rpc.tour.req.FavoritesReq;
import com.ctshk.rpc.tour.req.LocalHotSearchReq;
import com.ctshk.rpc.tour.req.ScenicJson;
import com.ctshk.rpc.tour.req.es.*;
import com.ctshk.rpc.tour.service.ISeriesTripAppService;
import com.ctshk.rpc.user.req.UserCollectionReq;
import com.ctshk.rpc.user.req.UserFootPointReq;
import com.ctshk.rpc.user.service.IUserCollectionService;
import com.ctshk.rpc.user.service.IUserFootPointService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.*;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 系列团行程 服务实现类
 * </p>
 *
 * @author 谢诗宏
 * @since 2021-01-12
 */
@Slf4j
@DubboService
public class SeriesTripAppServiceImpl extends ServiceImpl<SeriesTripMapper, SeriesTrip> implements ISeriesTripAppService, BaseService {

    /**
     * 行程信息服务
     */
    @Autowired
    private SeriesTripScheduleItineraryDayMapper seriesTripScheduleItineraryDayMapper;

    /**
     * 系列团--团期服务
     */
    @Autowired
    private SeriesTripScheduleServiceImpl seriesTripScheduleService;

    /**
     * 系列团--团期服务
     */
    @Autowired
    private SeriesTripScheduleMapper seriesTripScheduleMapper;

    /**
     * 行程--旅客类型
     */
    @Autowired
    private SeriesTripTouristTypeMapper seriesTripTouristTypeMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @DubboReference
    private IMainDataTourGroupAreaService mainDataTourGroupAreaService;

    @DubboReference
    private IMainDataTourGroupCodeService mainDataTourGroupCodeService;

    @DubboReference
    private IMainDataTourGroupService mainDataTourGroupService;

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private SeriesTripScheduleDefaultCostMapper seriesTripScheduleDefaultCostMapper;

    @Autowired
    private SeriesTripMapper seriesTripMapper;

    @Autowired
    private SeriesTripDefaultCostMapper seriesTripDefaultCostMapper;

    @Autowired
    private SeriesTripDefaultCostGroupMapper seriesTripDefaultCostGroupMapper;

    @Autowired
    private SeriesTripElasticSearchMapper seriesTripElasticSearchMapper;

    @Autowired
    private SeriesTripChannelOnlineSelfPlatformMapper tripChannelOnlineSelfPlatformMapper;

    @Autowired
    private SeriesTripScheduleChannelOnlineSelfPlatformMapper scheduleChannelOnlineSelfPlatformMapper;

    @Autowired
    private SeriesTripScheduleTeamRuleMapper seriesTripScheduleTeamRuleMapper;

    @Autowired
    private SeriesTripScheduleCustomCostMapper seriesTripScheduleCustomCostMapper;

    @Autowired
    private SeriesTripScheduleCustomSalePriceMapper seriesTripScheduleCustomSalePriceMapper;

    @Autowired
    private SeriesTripScheduleDefaultCostTouristTypeMapper seriesTripScheduleDefaultCostTouristTypeMapper;

    @Autowired
    private SeriesTripScheduleCustomTouristTypeMapper seriesTripScheduleCustomTouristTypeMapper;

    @Autowired
    private SeriesTripScheduleCustomCostCollectionMapper seriesTripScheduleCustomCostCollectionMapper;

    @Autowired
    private SeriesTripScheduleDefaultCostCollectionMapper seriesTripScheduleDefaultCostCollectionMapper;

    @Autowired
    private SeriesTripScheduleTouristTypeMapper seriesTripScheduleTouristTypeMapper;

    @Autowired
    private SeriesTripCustomCostMapper seriesTripCustomCostMapper;

    @DubboReference
    private IBaiDuApiService baiDuApiService;

    @DubboReference
    private IBranchOfficeService branchOfficeService;

    @DubboReference
    private ISeriesOrderService seriesOrderService;

    @DubboReference
    private IUserFootPointService userFootPointService;

    @Autowired
    private RedissonClient redissonClient;

    @DubboReference
    private ISysAreaService areaService;

    @DubboReference
    private IUserCollectionService userCollectionService;

    @DubboReference
    private INoticePositionService noticePositionService;

    @DubboReference
    private IBannerPositionService bannerPositionService;

    @Autowired
    private SeriesTripScheduleCustomFeeAdjustMapper customFeeAdjustMapper;

    @Autowired
    private SeriesTripScheduleDefaultFeeAdjustMapper defaultFeeAdjustMapper;

    /**
     * 旅行团 - 缓存数据初始化
     *
     * @return
     */
    @Override
    public Result tripInit() {
        // 删除es旧缓存数据
        seriesTripElasticSearchMapper.deleteAll();
        tripHomeCatalogDTO();
        // 初始化缓存
        updateTripCache(null);
        setUpdateTime(RedisConstants.TOUR_CACHE_UPDATE, LocalDateTime.now());
        return Result.success();
    }

    /**
     * 构建缓存数据
     *
     * @return
     */
    private Map<String, Map<Long, String>> buildCacheData(TripCacheUpdateReq req) {
        // 有效线上渠道列表
        List<SeriesTripChannelOnlineSelfPlatform> seriesTripChannelOnlineSelfPlatforms = tripChannelOnlineSelfPlatformMapper.selectList(Wrappers.<SeriesTripChannelOnlineSelfPlatform>lambdaQuery()
                .eq(SeriesTripChannelOnlineSelfPlatform::getIsDeleted, IsDeletedCode.NO.getCode())
                .gt(SeriesTripChannelOnlineSelfPlatform::getAmount, 0)
                .isNotNull(SeriesTripChannelOnlineSelfPlatform::getTripId)
                .isNotNull(SeriesTripChannelOnlineSelfPlatform::getChannelType));
        if (CollectionUtil.isEmpty(seriesTripChannelOnlineSelfPlatforms)) {
            return null;
        }

        List<Long> ids;
        List<SeriesTrip> seriesTrips = null;
        LocalDate now = LocalDate.now();
        Set<Long> channelIds = seriesTripChannelOnlineSelfPlatforms.stream().filter(item -> null != item.getChannelType()).map(item -> item.getChannelType()).collect(Collectors.toSet());
        if (ObjectUtils.isEmpty(req) || CollectionUtil.isEmpty(req.getIds())) {
            seriesTrips = seriesTripMapper.selectList(Wrappers.<SeriesTrip>lambdaQuery()
                    .eq(SeriesTrip::getIsDeleted, IsDeletedCode.NO.getCode())
                    .eq(SeriesTrip::getStatus, TripStatusCode.STATUS_3.getCode()));
        } else {
            LambdaQueryWrapper<SeriesTrip> queryWrapper = Wrappers.<SeriesTrip>lambdaQuery().eq(SeriesTrip::getIsDeleted, IsDeletedCode.NO.getCode());
            queryWrapper.eq(SeriesTrip::getStatus, TripStatusCode.STATUS_3.getCode());
            queryWrapper.ge(req.getStartTime() != null, SeriesTrip::getGmtModified, getUpdateTime());
            queryWrapper.eq(req.getIds() != null && req.getIds().size() == 1, SeriesTrip::getId, req.getIds().get(0));
            queryWrapper.in(req.getIds() != null && req.getIds().size() > 1, SeriesTrip::getId, req.getIds());
            seriesTrips = baseMapper.selectList(queryWrapper);
        }
        ids = seriesTrips.stream().map(item -> item.getId()).collect(Collectors.toList());

        /********************************************************************* 行程检索缓存(elasticSearch)所需数据 *************************************************************************/
        // 是否查询到行程结果列表
        if (CollectionUtil.isEmpty(seriesTrips)) return null;

        Set<Long> tripIds = new HashSet<>(ids);
        // 菜单主数据
        List<CatalogMainDataDTO> catalogMainData = mainDataTourGroupService.getCatalogMainData();

        // 行程团期
        List<SeriesTripSchedule> seriesTripSchedules = seriesTripScheduleMapper
                .selectList(Wrappers.<SeriesTripSchedule>lambdaQuery()
                        .eq(SeriesTripSchedule::getIsDeleted, IsDeletedCode.NO.getCode())
                        .between(SeriesTripSchedule::getScheduleStatus, ScheduleStatusCode.STATUS_1.getCode(), ScheduleStatusCode.STATUS_2.getCode())
                        .ge(SeriesTripSchedule::getDepartureDate, now)
                        .le(SeriesTripSchedule::getDepartureDate, now.plusMonths(5).with(TemporalAdjusters.lastDayOfMonth()))
                        .in(SeriesTripSchedule::getTripId, tripIds));

        // 行程最近7天销量
        ids = seriesTripSchedules.stream().map(item -> item.getId()).collect(Collectors.toList());
        List<SeriesOrderDTO> tripSaleCount = getTripSaleCount(ids);
        Map<Long, Integer> saleMap = new HashMap<>(tripIds.size());
        for (Long tripId : tripIds) {
            Set<Long> scheduleIds = seriesTripSchedules.stream()
                    .filter(item -> item.getTripId().longValue() == tripId.longValue()).map(item -> item.getId())
                    .collect(Collectors.toSet());

            int sale = tripSaleCount.stream().filter(item -> scheduleIds.contains(item.getScheduleId()))
                    .mapToInt(SeriesOrderDTO::getSaleCount).sum();
            saleMap.put(tripId, sale);
        }

        // 线上自营加幅策略
        List<SeriesTripChannelOnlineSelfPlatform> channelOnlineSelfPlatforms = seriesTripChannelOnlineSelfPlatforms
                .stream()
                .filter(item -> tripIds.contains(item.getTripId()))
                .collect(Collectors.toList());

        // 默认成本分摊
        List<SeriesTripDefaultCost> tripDefaultCosts = seriesTripDefaultCostMapper.selectList(Wrappers.<SeriesTripDefaultCost>lambdaQuery()
                .eq(SeriesTripDefaultCost::getIsDeleted, IsDeletedCode.NO.getCode())
                .gt(SeriesTripDefaultCost::getGroupCustomerPrice, 0)
                .in(SeriesTripDefaultCost::getTripId, tripIds));

        // 自定义成本分摊
        List<TripCustomCostDTO> tripCustomCost = seriesTripCustomCostMapper.queryTripCustomCost(tripIds, IsDeletedCode.NO.getCode());

        // 每日行程
        List<SeriesTripScheduleItineraryDay> seriesTripScheduleItineraryDays = seriesTripScheduleItineraryDayMapper
                .selectList(Wrappers.<SeriesTripScheduleItineraryDay>lambdaQuery()
                        .eq(SeriesTripScheduleItineraryDay::getIsDeleted, IsDeletedCode.NO.getCode())
                        .in(SeriesTripScheduleItineraryDay::getTripId, tripIds));

        // 构建es缓存数据
        List<TripCacheDTO> esTripCacheDTOS = buildElasticSearchCache(channelIds, seriesTrips, catalogMainData,
                seriesTripScheduleItineraryDays, seriesTripSchedules, channelOnlineSelfPlatforms, tripDefaultCosts,
                saleMap, tripCustomCost);

        Map<String, Map<Long, String>> result = null;
        if (CollectionUtil.isNotEmpty(esTripCacheDTOS)) {
            result = new HashMap<>();
            Map<Long, String> es = new HashMap<>();
            Map<Long, String> redis = new HashMap<>();
            /********************************************************************* 行程详情缓存（redis）所需数据 *************************************************************************/
            Set<Long> esTripIds = esTripCacheDTOS.stream().map(item -> item.getTripId()).collect(Collectors.toSet());
            List<Long> scheduleIds = seriesTripSchedules.stream().filter(item -> esTripIds.contains(item.getTripId()))
                    .map(item -> item.getId()).collect(Collectors.toList());

            // 过滤需要缓存的团期
            List<SeriesTripSchedule> tripSchedules = seriesTripSchedules.stream().filter(item -> esTripIds.contains(item.getTripId())).collect(Collectors.toList());

            // 过滤需要缓存的行程每日介绍详情
            List<SeriesTripScheduleItineraryDay> itineraryDays = seriesTripScheduleItineraryDays.stream().filter(item -> esTripIds.contains(item.getTripId())).collect(Collectors.toList());

            // 渠道所有有效团期加幅策略
            List<SeriesTripScheduleChannelOnlineSelfPlatform> scheduleOnlineSelfPlatforms = scheduleChannelOnlineSelfPlatformMapper
                    .selectList(Wrappers.<SeriesTripScheduleChannelOnlineSelfPlatform>lambdaQuery()
                            .eq(SeriesTripScheduleChannelOnlineSelfPlatform::getIsDeleted, IsDeletedCode.NO.getCode())
                            .in(SeriesTripScheduleChannelOnlineSelfPlatform::getChannelType, channelIds)
                            .in(SeriesTripScheduleChannelOnlineSelfPlatform::getScheduleId, scheduleIds));

            // 团期团队规则 - 最大可销售数量 - 用来计算剩余可售位数
            List<SeriesTripScheduleTeamRule> seriesTripScheduleTeamRules = seriesTripScheduleTeamRuleMapper
                    .selectList(Wrappers.<SeriesTripScheduleTeamRule>lambdaQuery()
                            .in(SeriesTripScheduleTeamRule::getScheduleId, scheduleIds));

            // 团期成本默认分摊
            List<SeriesTripScheduleDefaultCost> scheduleDefaultCosts = seriesTripScheduleDefaultCostMapper.selectList(Wrappers.<SeriesTripScheduleDefaultCost>lambdaQuery()
                    .eq(SeriesTripScheduleDefaultCost::getIsDeleted, IsDeletedCode.NO.getCode())
                    .gt(SeriesTripScheduleDefaultCost::getGroupCustomerPrice, 0)
                    .in(SeriesTripScheduleDefaultCost::getTripId, esTripIds)
                    .in(SeriesTripScheduleDefaultCost::getScheduleId, scheduleIds));
            Set<Long> scheduleDefaultCostIds = scheduleDefaultCosts.stream().map(item -> item.getId()).collect(Collectors.toSet());

            // 团期默认代收费用项
            List<SeriesTripScheduleDefaultCostCollection> seriesTripScheduleDefaultCostCollections = seriesTripScheduleDefaultCostCollectionMapper
                    .selectList(Wrappers.<SeriesTripScheduleDefaultCostCollection>lambdaQuery()
                            .or(w -> w.eq(SeriesTripScheduleDefaultCostCollection::getIsDeleted, IsDeletedCode.NO.getCode())
                                    .or().isNull(SeriesTripScheduleDefaultCostCollection::getIsDeleted))
                            .in(SeriesTripScheduleDefaultCostCollection::getTripId, esTripIds)
                            .in(SeriesTripScheduleDefaultCostCollection::getCostId, scheduleDefaultCostIds)
                            .in(SeriesTripScheduleDefaultCostCollection::getScheduleId, scheduleIds));

            // 团期默认调整费用
            List<SeriesTripScheduleDefaultFeeAdjust> seriesTripScheduleDefaultFeeAdjusts = defaultFeeAdjustMapper
                    .selectList(Wrappers.<SeriesTripScheduleDefaultFeeAdjust>lambdaQuery()
                            .or(w -> w.eq(SeriesTripScheduleDefaultFeeAdjust::getIsDeleted, IsDeletedCode.NO.getCode())
                                    .or().isNull(SeriesTripScheduleDefaultFeeAdjust::getIsDeleted))
                            .eq(SeriesTripScheduleDefaultFeeAdjust::getMainDataId, Constants.SINGLE_ROOM_ID)
                            .in(SeriesTripScheduleDefaultFeeAdjust::getScheduleId, scheduleIds));

            // 团期默认旅客类型
            List<SeriesTripScheduleDefaultCostTouristType> seriesTripScheduleDefaultCostTouristTypes = seriesTripScheduleDefaultCostTouristTypeMapper
                    .selectList(Wrappers.<SeriesTripScheduleDefaultCostTouristType>lambdaQuery()
                            .in(SeriesTripScheduleDefaultCostTouristType::getTripId, esTripIds)
                            .gt(SeriesTripScheduleDefaultCostTouristType::getTotalAmount, 0));

            // 团期成本自定义分摊
            List<SeriesTripScheduleCustomCost> scheduleCustomCosts = seriesTripScheduleCustomCostMapper.selectList(Wrappers.<SeriesTripScheduleCustomCost>lambdaQuery()
                    .eq(SeriesTripScheduleCustomCost::getIsDeleted, IsDeletedCode.NO.getCode())
                    .in(SeriesTripScheduleCustomCost::getTripId, esTripIds)
                    .in(SeriesTripScheduleCustomCost::getScheduleId, scheduleIds));

            List<SeriesTripScheduleCustomSalePrice> customSalePrices = new ArrayList<>();
            Set<Long> customCostIds = new HashSet<>();
            if (CollectionUtil.isNotEmpty(scheduleCustomCosts)) {
                customCostIds = scheduleCustomCosts.stream().map(item -> item.getId()).collect(Collectors.toSet());
                customSalePrices = seriesTripScheduleCustomSalePriceMapper.selectList(Wrappers.<SeriesTripScheduleCustomSalePrice>lambdaQuery()
                        .gt(SeriesTripScheduleCustomSalePrice::getGroupCustomerPrice, 0)
                        .in(customCostIds.size() > 0, SeriesTripScheduleCustomSalePrice::getCostId, customCostIds));
            }

            // 团期自定义代收费用
            List<SeriesTripScheduleCustomCostCollection> seriesTripScheduleCustomCostCollections = seriesTripScheduleCustomCostCollectionMapper
                    .selectList(Wrappers.<SeriesTripScheduleCustomCostCollection>lambdaQuery()
                            .or(w -> w.eq(SeriesTripScheduleCustomCostCollection::getIsDeleted, IsDeletedCode.NO.getCode())
                                    .or().isNull(SeriesTripScheduleCustomCostCollection::getIsDeleted))
                            .in(customCostIds.size() > 0, SeriesTripScheduleCustomCostCollection::getCostId, customCostIds)
                            .in(SeriesTripScheduleCustomCostCollection::getTripId, esTripIds)
                            .in(SeriesTripScheduleCustomCostCollection::getScheduleId, scheduleIds));

            // 团期自定义旅客类型
            List<SeriesTripScheduleCustomTouristType> seriesTripScheduleCustomTouristTypes = seriesTripScheduleCustomTouristTypeMapper
                    .selectList(Wrappers.<SeriesTripScheduleCustomTouristType>lambdaQuery()
                            .in(SeriesTripScheduleCustomTouristType::getTripId, esTripIds)
                            .gt(SeriesTripScheduleCustomTouristType::getTotalAmount, 0));

            // 团期旅客类型
            List<SeriesTripScheduleTouristType> seriesTripScheduleTouristTypes = seriesTripScheduleTouristTypeMapper
                    .selectList(Wrappers.<SeriesTripScheduleTouristType>lambdaQuery()
                            .eq(SeriesTripScheduleTouristType::getIsDeleted, IsDeletedCode.NO.getCode())
                            .in(SeriesTripScheduleTouristType::getScheduleId, scheduleIds)
                            .in(SeriesTripScheduleTouristType::getTripId, esTripIds));

            // 团期自定义调整费用
            List<ScheduleCustomFeeAdjustAndTouristDTO> customFeeAdjustAndTouristDTOS = customFeeAdjustMapper.selectCustomFeeAdjustList(scheduleIds, tripIds, Constants.SINGLE_ROOM_ID);

            List<SeriesTrip> tripList = seriesTrips.stream().filter(item -> esTripIds.contains(item.getId())).collect(Collectors.toList());

            List<TripDetailsCacheDTO> redisDetailsCaches = new ArrayList<>();
            for (Long channelId : channelIds) {
                // 过滤需要缓存的团期加幅策略
                List<SeriesTripScheduleChannelOnlineSelfPlatform> onlineSelfPlatforms = scheduleOnlineSelfPlatforms.stream().filter(item -> item.getChannelType().equals(channelId)).collect(Collectors.toList());
                // 构建redis缓存
                Map<Long, String> redisCache = buildRedisCache(channelId, tripList, tripSchedules, itineraryDays,
                        onlineSelfPlatforms, seriesTripScheduleTeamRules, scheduleDefaultCosts, scheduleCustomCosts,
                        customSalePrices, tripDefaultCosts, channelOnlineSelfPlatforms, seriesTripScheduleDefaultCostCollections,
                        seriesTripScheduleCustomCostCollections, seriesTripScheduleDefaultCostTouristTypes,
                        seriesTripScheduleCustomTouristTypes, seriesTripScheduleTouristTypes, catalogMainData,
                        customFeeAdjustAndTouristDTOS, seriesTripScheduleDefaultFeeAdjusts);
                redis.putAll(redisCache);

                String schedule = redisCache.get(channelId);
                redisDetailsCaches.addAll(JSONArray.parseArray(schedule, TripDetailsCacheDTO.class));
            }

            // 筛掉redis缓存中不存在的行程
            List<Long> redisTripIds = redisDetailsCaches.stream().map(item -> item.getTripId()).collect(Collectors.toList());
            List<TripCacheDTO> cacheDTOS = esTripCacheDTOS.stream().filter(item -> redisTripIds.contains(item.getTripId())).collect(Collectors.toList());

            // 删除无效缓存
            Set<Long> delete = tripIds.stream().filter(item -> !redisTripIds.contains(item)).collect(Collectors.toSet());
            deleteCache(delete, channelIds);

            es.put(1L, JSON.toJSONString(cacheDTOS));
            result.put("es", es);
            result.put("redis", redis);
        }
        return result;
    }

    /**
     * 构建es缓存数据
     *
     * @param channelIds
     * @param trips
     * @return
     */
    private List<TripCacheDTO> buildElasticSearchCache(Set<Long> channelIds, List<SeriesTrip> trips,
                                                       List<CatalogMainDataDTO> mainDataTourGroup,
                                                       List<SeriesTripScheduleItineraryDay> seriesTripScheduleItineraryDays,
                                                       List<SeriesTripSchedule> seriesTripSchedules,
                                                       List<SeriesTripChannelOnlineSelfPlatform> channelOnlineSelfPlatforms,
                                                       List<SeriesTripDefaultCost> tripDefaultCosts,
                                                       Map<Long, Integer> saleMap,
                                                       List<TripCustomCostDTO> tripCustomCost) {
        List<TripCacheDTO> tripCaches = new ArrayList<>();

        // 获取行程数据
        for (SeriesTrip trip : trips) {
            // 行程所属旅团
            List<SeriesTripSchedule> schedules = seriesTripSchedules.stream().filter(item -> item.getTripId().longValue() == trip.getId().longValue()).collect(Collectors.toList());
            if (CollectionUtil.isEmpty(schedules)) {
                continue;
            }

            // 行程下旅团出行日期
            LocalDate now = LocalDate.now();
            TripCacheDTO tripCache = EntityUtil.copy(trip, TripCacheDTO.class);
            List<LocalDate> dates = schedules.stream()
                    .filter(item -> item.getTripId().longValue() == trip.getId().longValue()
                            && (item.getDepartureDate().compareTo(now) == 0 || item.getDepartureDate().isAfter(now)))
                    .map(item -> item.getDepartureDate())
                    .collect(Collectors.toList());
            // 没有团期不缓存
            if (ObjectUtils.isEmpty(dates)) continue;

            tripCache.setDepartureDate(dates);
            tripCache.setTripId(trip.getId());
            tripCache.setIsHot(trip.getIsHot() == null ? 0 : trip.getIsHot());

            // 默认封面图
            if (ObjectUtils.isEmpty(trip.getCoverPhotoUrl())) {
                if (CollectionUtil.isNotEmpty(trip.getRotatePicturesJson())) {
                    tripCache.setCoverPhotoUrl(trip.getRotatePicturesJson().get(0));
                }
            }

            // 行程分组编码
            for (CatalogMainDataDTO tourGroup : mainDataTourGroup) {
                if (Long.parseLong(tourGroup.getRegionId()) == trip.getTourTypeId().longValue()) {
                    tripCache.setTourTypeCode(tourGroup.getTourGroupCode());
                    break;
                }
            }

            // 目的地
            String destination = getDestination(trip.getDestination());
            tripCache.setDestination(destination);

            // 行程景点信息
            List<SeriesTripScheduleItineraryDay> days = seriesTripScheduleItineraryDays.stream().filter(item -> item.getTripId().longValue() == trip.getId().longValue()).collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(days)) {
                List<ScenicJson> scenicJsons = new ArrayList<>();
                for (SeriesTripScheduleItineraryDay day : days) {
                    if (null != day.getScenicJson()) {
                        String str = JSON.toJSONString(day.getScenicJson());
                        scenicJsons.addAll(JSONArray.parseArray(str, ScenicJson.class));
                    }
                }
                // 景点去重
                List<ScenicJsonCacheDTO> scenicJsonCacheDTOS = EntityUtil.copyList(scenicJsons, ScenicJsonCacheDTO.class);
                Set<ScenicJsonCacheDTO> scenicJsonCache = new HashSet<>(scenicJsonCacheDTOS);
                tripCache.setScenicJson(new ArrayList<>(scenicJsonCache));
            }

            // 行程旅团信息
            if (schedules != null && schedules.size() > 0) {
                tripCache.setSchedules(EntityUtil.copyList(schedules, SeriesTripScheduleCacheDTO.class));
            }

            // 最近30天是否成团
            Long isTeam = 0L;
            isTeam = schedules.stream().filter(item ->
                    item.getTripId().longValue() == trip.getId().longValue()
                            && item.getScheduleStatus() == 2
                            && (item.getDepartureDate().isEqual(now)
                            || (item.getDepartureDate().isAfter(now) && item.getDepartureDate().isBefore(now.plusDays(30))))).count();
            tripCache.setIsTeam(isTeam > 0 ? 0 : 1);

            // 最近30天是否可预定
            Long isReserve = 0L;
            if (isTeam == 0) {
                isReserve = schedules.stream().filter(item ->
                        item.getTripId().longValue() == trip.getId().longValue()
                                && item.getScheduleStatus() == 1
                                && (item.getDepartureDate().compareTo(now) == 0
                                || (item.getDepartureDate().isAfter(now) && item.getDepartureDate().isBefore(now.plusDays(30))))).count();
                tripCache.setIsReserve(isTeam > 0 ? 0 : isReserve > 0 ? 0 : 1);
            }

            // 行程最近7天销量
            tripCache.setSaleCount(saleMap.get(trip.getId()));

            // 默认展示价格
            List<TripBasePriceDTO> tripBasePriceDTOS = new ArrayList<>();
            for (Long channelId : channelIds) {
                TripBasePriceDTO basePrice = new TripBasePriceDTO();
                basePrice.setChannelId(channelId);
                // 行程渠道加幅价
                for (SeriesTripChannelOnlineSelfPlatform onlineSelfPlatform : channelOnlineSelfPlatforms) {
                    if (onlineSelfPlatform.getTripId().equals(trip.getId()) && onlineSelfPlatform.getChannelType().equals(channelId)) {
                        if (ObjectUtils.isEmpty(trip.getCostSharingType()) || trip.getCostSharingType() == 1) {
                            for (SeriesTripDefaultCost defaultCost : tripDefaultCosts) {
                                if (defaultCost.getTripId().equals(trip.getId())) {
                                    basePrice.setPrice(defaultCost.getGroupCustomerPrice());
                                    break;
                                }
                            }
                        } else {
                            for (TripCustomCostDTO customCostDTO : tripCustomCost) {
                                if (customCostDTO.getTripId().longValue() == trip.getId().longValue()) {
                                    basePrice.setPrice(customCostDTO.getGroupCustomerPrice());
                                    break;
                                }
                            }
                        }
                        if (ObjectUtils.isEmpty(basePrice.getPrice())) {
                            continue;
                        }
                        BigDecimal tripBasePrice = channelIncrease(onlineSelfPlatform.getAdjustType(), onlineSelfPlatform.getAmount(), basePrice.getPrice());
                        basePrice.setPrice(tripBasePrice);
                        tripBasePriceDTOS.add(basePrice);
                        break;
                    }
                }
            }

            if (CollectionUtil.isNotEmpty(tripBasePriceDTOS)) {
                tripCache.setBasePrice(tripBasePriceDTOS);
                tripCaches.add(tripCache);
            }
        }
        return tripCaches;
    }

    /**
     * 构建redis缓存数据
     *
     * @param channelId
     * @param trips
     * @return
     */
    private Map<Long, String> buildRedisCache(Long channelId, List<SeriesTrip> trips,
                                              List<SeriesTripSchedule> seriesTripSchedules,
                                              List<SeriesTripScheduleItineraryDay> seriesTripScheduleItineraryDays,
                                              List<SeriesTripScheduleChannelOnlineSelfPlatform> onlineSelfPlatforms,
                                              List<SeriesTripScheduleTeamRule> tripScheduleTeamRules,
                                              List<SeriesTripScheduleDefaultCost> scheduleDefaultCosts,
                                              List<SeriesTripScheduleCustomCost> seriesTripScheduleCustomCosts,
                                              List<SeriesTripScheduleCustomSalePrice> customSalePrices,
                                              List<SeriesTripDefaultCost> tripDefaultCostList,
                                              List<SeriesTripChannelOnlineSelfPlatform> tripChannelOnlineSelfPlatformList,
                                              List<SeriesTripScheduleDefaultCostCollection> seriesTripScheduleDefaultCostCollections,
                                              List<SeriesTripScheduleCustomCostCollection> seriesTripScheduleCustomCostCollections,
                                              List<SeriesTripScheduleDefaultCostTouristType> seriesTripScheduleDefaultCostTouristTypes,
                                              List<SeriesTripScheduleCustomTouristType> seriesTripScheduleCustomTouristTypes,
                                              List<SeriesTripScheduleTouristType> seriesTripScheduleTouristTypes,
                                              List<CatalogMainDataDTO> catalogMainData,
                                              List<ScheduleCustomFeeAdjustAndTouristDTO> customFeeAdjustAndTouristDTOS,
                                              List<SeriesTripScheduleDefaultFeeAdjust> seriesTripScheduleDefaultFeeAdjusts) {
        if (CollectionUtil.isEmpty(trips)) {
            return null;
        }
        Map<Long, String> redisMap = new HashMap<>();
        List<TripDetailsCacheDTO> detailsCaches = new ArrayList<>();
        LocalDate now = LocalDate.now();

        // 组装行程详情数据
        for (SeriesTrip trip : trips) {
            // 当前行程有效团期
            List<SeriesTripSchedule> tripSchedules = seriesTripSchedules.stream().filter(item ->
                    item.getTripId().longValue() == trip.getId().longValue()
                            && (item.getDepartureDate().compareTo(now) == 0 || item.getDepartureDate().isAfter(now)))
                    .collect(Collectors.toList());
            // 没有团期不缓存
            if (CollectionUtil.isEmpty(tripSchedules)) {
                continue;
            }
            Set<Long> scheduleIds = tripSchedules.stream().map(item -> item.getId()).collect(Collectors.toSet());

            TripDetailsCacheDTO detailsCache = EntityUtil.copy(trip, TripDetailsCacheDTO.class);
            detailsCache.setTripId(trip.getId());
            detailsCache.setId(trip.getId());
            detailsCache.setCoverPhotoUrl(trip.getCoverPhotoUrl());
            detailsCache.setVideoUrl(trip.getVideoUrl());
            detailsCache.setRotatePicturesJson(trip.getRotatePicturesJson());
            detailsCache.setIsHot(detailsCache.getIsHot() == null ? 2 : detailsCache.getIsHot());

            // 行程每日介绍
            List<SeriesTripScheduleItineraryDay> days = seriesTripScheduleItineraryDays.stream()
                    .filter(item -> item.getTripId().longValue() == trip.getId().longValue())
                    .collect(Collectors.toList());
            List<TripScheduleItineraryDayDTO> tripScheduleItineraryDayDTOS = EntityUtil.copyList(days, TripScheduleItineraryDayDTO.class);
            detailsCache.setTripItineraryDays(tripScheduleItineraryDayDTOS);

            // 当前行程所属团期渠道加幅策略
            List<SeriesTripScheduleChannelOnlineSelfPlatform> scheduleOnlineSelfPlatforms = onlineSelfPlatforms.stream()
                    .filter(item -> scheduleIds.contains(item.getScheduleId()))
                    .collect(Collectors.toList());

            // 团期自定义成本分摊列表
            List<SeriesTripScheduleCustomSalePrice> scheduleCustomSalePrices = null;
            if (CollectionUtil.isNotEmpty(seriesTripScheduleCustomCosts)) {
                // 自定义成本分摊id
                Set<Long> scheduleCustomCostsIds = seriesTripScheduleCustomCosts.stream()
                        .filter(item -> scheduleIds.contains(item.getScheduleId()))
                        .map(item -> item.getId())
                        .collect(Collectors.toSet());

                // 自定义成本价格
                scheduleCustomSalePrices = customSalePrices.stream()
                        .filter(item -> scheduleIds.contains(item.getScheduleId())
                                && scheduleCustomCostsIds.contains(item.getCostId()))
                        .collect(Collectors.toList());
            }

            // 基础展示价格
            for (SeriesTripDefaultCost defaultCost : tripDefaultCostList) {
                if (defaultCost.getTripId().equals(trip.getId())) {
                    BigDecimal price = defaultCost.getGroupCustomerPrice();
                    List<SeriesTripChannelOnlineSelfPlatform> onlineSelfPlatform = tripChannelOnlineSelfPlatformList.stream()
                            .filter(item -> item.getTripId().equals(trip.getId()) && item.getChannelType().equals(channelId))
                            .collect(Collectors.toList());
                    if (ObjectUtils.isNotEmpty(onlineSelfPlatform)) {
                        SeriesTripChannelOnlineSelfPlatform seriesTripChannelOnlineSelfPlatform = onlineSelfPlatform.get(0);
                        price = channelIncrease(seriesTripChannelOnlineSelfPlatform.getAdjustType(), seriesTripChannelOnlineSelfPlatform.getAmount(), price);
                    }
                    detailsCache.setBasePrice(price.add(defaultCost.getCollectionFee() != null ? defaultCost.getCollectionFee() : BigDecimal.ZERO));
                    break;
                }
            }

            // 分组编码
            for (CatalogMainDataDTO mainDataTourGroupDTO : catalogMainData) {
                if (Long.parseLong(mainDataTourGroupDTO.getRegionId()) == trip.getTourTypeId().longValue()) {
                    detailsCache.setTourTypeCode(mainDataTourGroupDTO.getTourGroupCode());
                }
            }

            // 团期日期、价格、状态详情列表
            List<ScheduleInfoCacheDTO> dateListDTOS = new ArrayList<>();
            for (SeriesTripSchedule schedule : tripSchedules) {
                ScheduleInfoCacheDTO info = EntityUtil.copy(schedule, ScheduleInfoCacheDTO.class);
                info.setDepartureDate(schedule.getDepartureDate());
                // 团期团队规则
                List<SeriesTripScheduleTeamRule> rules = tripScheduleTeamRules.stream()
                        .filter(item -> item.getScheduleId().longValue() == schedule.getId().longValue())
                        .collect(Collectors.toList());
                // 没有团队规则，无法获取规则信息，不缓存
                if (CollectionUtil.isEmpty(rules)) {
                    continue;
                }
                // 如果有团期团队规则，用最大位数 - 当前已售位数
                SeriesTripScheduleTeamRule scheduleTeamRule = rules.get(0);
                if (ObjectUtils.isEmpty(scheduleTeamRule.getMaxPeople()) && ObjectUtils.isEmpty(scheduleTeamRule.getMinPeople())) {
                    continue;
                }

                int remain = 0;
                if (ObjectUtils.isNotEmpty(schedule.getLockedTouristNum())) {
                    if (ObjectUtils.isNotEmpty(scheduleTeamRule.getMaxPeople())) {
                        remain = scheduleTeamRule.getMaxPeople() - schedule.getLockedTouristNum();
                    } else {
                        remain = scheduleTeamRule.getMinPeople() - schedule.getLockedTouristNum();
                    }
                } else {
                    if (ObjectUtils.isNotEmpty(scheduleTeamRule.getMaxPeople())) {
                        remain = scheduleTeamRule.getMaxPeople();
                    } else {
                        remain = scheduleTeamRule.getMinPeople();
                    }
                }

                // 没有余位不缓存
                if (remain <= 0) {
                    continue;
                }
                info.setRemain(remain);
                Integer nightDays = ObjectUtils.isEmpty(schedule.getNightDays()) || schedule.getNightDays() <= 0 ? 0 : schedule.getNightDays();
                Integer occupyBedNum = ObjectUtils.isEmpty(scheduleTeamRule.getOccupyBedNum()) || nightDays <= 0 ? 0 : scheduleTeamRule.getOccupyBedNum();
                Integer notOccupyBedNum = ObjectUtils.isEmpty(scheduleTeamRule.getNotOccupyBedNum()) || nightDays <= 0 ? 0 : scheduleTeamRule.getNotOccupyBedNum();
                info.setOccupyBedNum(occupyBedNum);
                info.setNotOccupyBedNum(notOccupyBedNum);
                info.setNightDays(nightDays);

                //旅客类型
                List<TripTouristTypeDTO> touristTypes = new ArrayList<>();
                if (ObjectUtils.isNotEmpty(scheduleTeamRule)) {
                    for (SeriesTripScheduleTouristType touristType : seriesTripScheduleTouristTypes) {
                        if (touristType.getScheduleId().longValue() == schedule.getId().longValue()
                                && touristType.getRuleId().longValue() == scheduleTeamRule.getId().longValue()) {
                            TripTouristTypeDTO tripTouristTypeDTO = new TripTouristTypeDTO();
                            tripTouristTypeDTO.setId(touristType.getMainDataId());
                            tripTouristTypeDTO.setName(touristType.getName());
                            tripTouristTypeDTO.setAgeBegin(touristType.getAgeBegin());
                            tripTouristTypeDTO.setAgeEnd(touristType.getAgeEnd());
                            tripTouristTypeDTO.setSort(touristType.getSort());
                            tripTouristTypeDTO.setIsOccupyBed(touristType.getIsOccupyBed());
                            touristTypes.add(tripTouristTypeDTO);
                        }
                    }
                }
                info.setTouristTypes(touristTypes);

                // 团期渠道加幅
                List<SeriesTripScheduleChannelOnlineSelfPlatform> scheduleChannelSelfPlatform = scheduleOnlineSelfPlatforms.stream()
                        .filter(item -> item.getChannelType().longValue() == channelId && item.getScheduleId().longValue() == schedule.getId().longValue())
                        .collect(Collectors.toList());

                // 成本分摊类型直客价价
                BigDecimal price = BigDecimal.ZERO;

                // 分摊类型：1：默认，2：自定义
                if (schedule.getCostSharingType().intValue() == CostSharingType.DEFAULT.getCode().intValue()) {
                    // 团期默认成本分摊列表
                    List<SeriesTripScheduleDefaultCost> defaultCostsList = scheduleDefaultCosts.stream()
                            .filter(item -> item.getScheduleId().longValue() == schedule.getId().longValue())
                            .collect(Collectors.toList());

                    // 行程默认直客价为0不缓存
                    if (CollectionUtil.isEmpty(defaultCostsList) || defaultCostsList.get(0).getGroupCustomerPrice().compareTo(BigDecimal.ZERO) <= 0)
                        continue;
                    SeriesTripScheduleDefaultCost defaultCost = defaultCostsList.get(0);
                    price = defaultCost.getGroupCustomerPrice();
                    info.setPrice(CollectionUtil.isEmpty(scheduleChannelSelfPlatform) ? price.setScale(0, BigDecimal.ROUND_UP) : channelIncrease(scheduleChannelSelfPlatform.get(0).getAdjustType(), scheduleChannelSelfPlatform.get(0).getAmount(), price));

                    // 代收项
                    List<SeriesTripScheduleDefaultCostCollection> defaultCostCollections = seriesTripScheduleDefaultCostCollections
                            .stream()
                            .filter(item -> item.getScheduleId().longValue() == schedule.getId().longValue()
                                    && item.getCostId().longValue() == defaultCost.getId().longValue())
                            .collect(Collectors.toList());

                    // 过滤当前团期的 团期默认模板代收费用客人类型
                    Set<SeriesTripScheduleDefaultCostTouristType> defaultCostTouristTypes = seriesTripScheduleDefaultCostTouristTypes
                            .stream()
                            .filter(item -> item.getScheduleId().equals(schedule.getId()))
                            .collect(Collectors.toSet());

                    // 默认调整项
                    List<SeriesTripScheduleDefaultFeeAdjust> defaultFeeAdjusts = seriesTripScheduleDefaultFeeAdjusts
                            .stream()
                            .filter(item -> item.getScheduleId().longValue() == schedule.getId().longValue())
                            .collect(Collectors.toList());

                    // 代收项费用 & 调整项费用
                    for (TripTouristTypeDTO touristType : touristTypes) {
                        // 代收项费用
                        List<TripCostCollectionDTO> costList = new ArrayList<>();
                        for (SeriesTripScheduleDefaultCostCollection defaultCollection : defaultCostCollections) {
                            for (SeriesTripScheduleDefaultCostTouristType defaultCostTouristType : defaultCostTouristTypes) {
                                if (defaultCostTouristType.getCollectionId().longValue() == defaultCollection.getId().longValue()
                                        && touristType.getId().longValue() == defaultCostTouristType.getTouristTypeId().longValue()
                                        && ObjectUtils.isNotEmpty(defaultCostTouristType.getTotalAmount())
                                        && defaultCostTouristType.getTotalAmount().compareTo(BigDecimal.ZERO) == 1) {
                                    TripCostCollectionDTO tripCostCollectionDTO = new TripCostCollectionDTO();
                                    tripCostCollectionDTO.setId(defaultCollection.getMainDataId());
                                    tripCostCollectionDTO.setName(defaultCollection.getProjectName() + "（" + defaultCostTouristType.getTouristTypeName() + "）");
                                    tripCostCollectionDTO.setPrice(defaultCostTouristType.getTotalAmount().multiply(new BigDecimal(defaultCollection.getExchangeRate())).setScale(0, BigDecimal.ROUND_UP));
                                    costList.add(tripCostCollectionDTO);
                                }
                            }
                        }
                        touristType.setCostList(costList);

                        // 默认调整项费用
                        List<TripCostCollectionDTO> adjustFeeList = new ArrayList<>();
                        for (SeriesTripScheduleDefaultFeeAdjust adjust : defaultFeeAdjusts) {
                            if (adjust.getMainDataId().longValue() == Constants.SINGLE_ROOM_ID.longValue()
                                    && touristType.getIsOccupyBed().intValue() == IsOccupyBedCode.NO.getCode().intValue()) {
                                continue;
                            }
                            TripCostCollectionDTO tripCostCollectionDTO = new TripCostCollectionDTO();
                            tripCostCollectionDTO.setId(adjust.getMainDataId());
                            tripCostCollectionDTO.setName(adjust.getProjectName());
                            tripCostCollectionDTO.setPrice(adjust.getCostAmount().multiply(new BigDecimal(adjust.getExchangeRate())).setScale(0, BigDecimal.ROUND_UP));
                            tripCostCollectionDTO.setAdjustType(adjust.getAdjustType());
                            adjustFeeList.add(tripCostCollectionDTO);
                        }
                        touristType.setAdjustFeeList(adjustFeeList);
                    }
                } else {
                    // 自定义成本分摊
                    List<SeriesTripScheduleCustomSalePrice> customSalePricesList = scheduleCustomSalePrices.stream()
                            .filter(item -> item.getScheduleId().longValue() == schedule.getId().longValue())
                            .collect(Collectors.toList());
                    // 未获取到团期有效自定义分摊 不缓存
                    if (CollectionUtil.isEmpty(customSalePricesList)) continue;

                    SeriesTripScheduleCustomSalePrice customSalePrice = customSalePricesList.get(0);
                    price = customSalePrice.getGroupCustomerPrice();
                    // 自定义成本为0 不缓存
                    if (price.compareTo(BigDecimal.ZERO) <= 0) continue;
                    info.setPrice(CollectionUtil.isEmpty(scheduleChannelSelfPlatform) ? price.setScale(0, BigDecimal.ROUND_UP) : channelIncrease(scheduleChannelSelfPlatform.get(0).getAdjustType(), scheduleChannelSelfPlatform.get(0).getAmount(), price));

                    // 代收项
                    List<SeriesTripScheduleCustomCostCollection> customCostCollections = seriesTripScheduleCustomCostCollections
                            .stream()
                            .filter(item -> item.getScheduleId().equals(schedule.getId())
                                    && item.getCostId().equals(customSalePrice.getCostId()))
                            .collect(Collectors.toList());

                    // 过滤当前行程 当前团期的 团期自定义模板代收费用客人类型
                    Set<SeriesTripScheduleCustomTouristType> customCostTouristTypes = seriesTripScheduleCustomTouristTypes
                            .stream().filter(item -> item.getScheduleId().equals(schedule.getId()))
                            .collect(Collectors.toSet());

                    // 调整项&调整项费用
                    List<ScheduleCustomFeeAdjustAndTouristDTO> customFeeAdjustAndTouristDTOList = customFeeAdjustAndTouristDTOS
                            .stream()
                            .filter(item -> item.getScheduleId().longValue() == schedule.getId().longValue()
                                    && item.getCostId().longValue() == customSalePrice.getCostId().longValue()
                                    && customSalePrice.getDateSubsectionId().longValue() == item.getDateSubsectionId().longValue())
                            .collect(Collectors.toList());

                    // 代收项费用 & 调整项费用
                    for (TripTouristTypeDTO touristType : touristTypes) {
                        // 代收费用
                        List<TripCostCollectionDTO> costList = new ArrayList<>();
                        for (SeriesTripScheduleCustomCostCollection customCollection : customCostCollections) {
                            for (SeriesTripScheduleCustomTouristType customCostTouristType : customCostTouristTypes) {
                                if (customCostTouristType.getCollectionId().equals(customCollection.getId())
                                        && touristType.getId().equals(customCostTouristType.getTouristTypeId())
                                        && ObjectUtils.isNotEmpty(customCostTouristType.getTotalAmount())
                                        && customCostTouristType.getTotalAmount().compareTo(BigDecimal.ZERO) == 1) {
                                    TripCostCollectionDTO tripCostCollectionDTO = new TripCostCollectionDTO();
                                    tripCostCollectionDTO.setName(customCollection.getProjectName() + "（" + customCostTouristType.getTouristTypeName() + "）");
                                    tripCostCollectionDTO.setPrice(customCostTouristType.getTotalAmount().multiply(new BigDecimal(customCollection.getExchangeRate())).setScale(0, BigDecimal.ROUND_UP));
                                    costList.add(tripCostCollectionDTO);
                                }
                            }
                        }
                        touristType.setCostList(costList);

                        // 调整项费用
                        List<ScheduleCustomFeeAdjustAndTouristDTO> scheduleCustomFeeAdjustAndTouristDTOS = customFeeAdjustAndTouristDTOList.stream()
                                .filter(item -> item.getTouristTypeId().longValue() == touristType.getId().longValue())
                                .collect(Collectors.toList());

                        List<TripCostCollectionDTO> adjustFeeList = new ArrayList<>();
                        for (ScheduleCustomFeeAdjustAndTouristDTO feeAdjustAndTouristDTO : scheduleCustomFeeAdjustAndTouristDTOS) {
                            TripCostCollectionDTO tripCostCollectionDTO = new TripCostCollectionDTO();
                            tripCostCollectionDTO.setId(feeAdjustAndTouristDTO.getMainDataId());
                            String name = feeAdjustAndTouristDTO.getMainDataId().equals(Constants.SINGLE_ROOM_ID) ? feeAdjustAndTouristDTO.getProjectName() : feeAdjustAndTouristDTO.getProjectName() + "(" + touristType.getName() + ")";
                            tripCostCollectionDTO.setName(name);
                            tripCostCollectionDTO.setPrice(feeAdjustAndTouristDTO.getPrice().multiply(new BigDecimal(feeAdjustAndTouristDTO.getExchangeRate())).setScale(0, BigDecimal.ROUND_UP));
                            tripCostCollectionDTO.setAdjustType(feeAdjustAndTouristDTO.getAdjustType());
                            adjustFeeList.add(tripCostCollectionDTO);

                        }
                        touristType.setAdjustFeeList(adjustFeeList);
                    }
                }
                dateListDTOS.add(info);
            }
            // 可售团期不为空的加入缓存
            if (CollectionUtil.isNotEmpty(dateListDTOS)) {
                detailsCache.setDateListDTOS(dateListDTOS);
                detailsCaches.add(detailsCache);
            }
        }
        redisMap.put(channelId, JSON.toJSONString(detailsCaches));
        return redisMap;
    }

    /**
     * 行程目的地截取
     *
     * @param destination
     * @return
     */
    private String getDestination(String destination) {
        if (destination == null) {
            return null;
        }
        // 行程目的地字符串截取
        String[] split = destination.split(StringPool.SPACE);
        return split.length > 0 ? split[split.length - 1] : null;
    }

    private void deleteCache(Set<Long> delete, Set<Long> channelIds) {
        // 删除无效缓存
        if (CollectionUtil.isNotEmpty(delete)) {
            List<TripCache> tripCaches = new ArrayList<>();
            for (Long channelId : channelIds) {
                // 删除redis无效缓存
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + channelId + ":");
                for (Long tripId : delete) {
                    hashOps.delete(tripId.toString());
                    tripCaches.add(new TripCache().setId(tripId));
                }
            }

            // 删除es无效缓存
            seriesTripElasticSearchMapper.deleteAll(tripCaches);
        }
    }

    /**
     * 旅行团首页分组信息
     *
     * @return
     */
    @Override
    public List<SeriesTripGroupDTO> getHomeGroupInfo(Long channelId) {
        // 从redis获取分组列表
        String str = redisTemplate.opsForValue().get(RedisConstants.TOUR_HOME_CATAGORY);
        List<TripHomeCatalogDTO> tripHomeCatalogDTOS = JSONArray.parseArray(str, TripHomeCatalogDTO.class);
        Set<String> groupIds = tripHomeCatalogDTOS.stream().map(item -> item.getId()).collect(Collectors.toSet());
        List<SeriesTripGroupDTO> seriesTripGroups = new ArrayList<>();
        for (String groupId : groupIds) {
            List<TripGroup> tripGroups = new ArrayList<>();
            SeriesTripGroupDTO tripGroupDTO = new SeriesTripGroupDTO();
            tripGroupDTO.setTourTypeId(groupId);
            for (TripHomeCatalogDTO homeCatalogDTO : tripHomeCatalogDTOS) {
                if (homeCatalogDTO.getId().equals(groupId)) {
                    tripGroupDTO.setTourTypeNam(homeCatalogDTO.getName());
                }
            }

            // es检索条件
            SearchSourceBuilder searchSource = new SearchSourceBuilder();
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolQuery.must(QueryBuilders.matchQuery("tourTypeCode", groupId));

            BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
            nestedBoolQuery.must(QueryBuilders.matchQuery("basePrice.channelId", channelId));
            nestedBoolQuery.must(QueryBuilders.rangeQuery("basePrice.price").gt(0));
            boolQuery.must(QueryBuilders.nestedQuery("basePrice", nestedBoolQuery, ScoreMode.None));

            boolQuery.should(QueryBuilders.matchQuery("isHot", 1));
            boolQuery.should(QueryBuilders.matchQuery("isTeam", 0));
            boolQuery.should(QueryBuilders.matchQuery("isReserve", 0));
            searchSource.sort(new FieldSortBuilder("_score").order(SortOrder.DESC));

            searchSource.query(boolQuery);
            searchSource.size(7);
            SearchRequest searchRequest = new SearchRequest().source(searchSource);
            try {
                // 查询并解析es检索结果
                SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
                SearchHit[] hits = response.getHits().getHits();
                for (SearchHit hit : hits) {
                    String obj = hit.getSourceAsString();
                    TripCacheDTO tripCacheDTO = JSON.parseObject(obj, TripCacheDTO.class);
                    TripGroup tripGroup = new TripGroup();
                    tripGroup.setCoverPhotoUrl(tripCacheDTO.getCoverPhotoUrl());
                    tripGroup.setTitle(tripCacheDTO.getTitle());
                    for (TripBasePriceDTO basePriceDTO : tripCacheDTO.getBasePrice()) {
                        if (basePriceDTO.getChannelId().equals(channelId)) {
                            tripGroup.setBasePrice(basePriceDTO.getPrice());
                        }
                    }
                    tripGroup.setTripId(tripCacheDTO.getTripId());
                    tripGroups.add(tripGroup);
                }
                tripGroupDTO.setTrips(tripGroups);
                seriesTripGroups.add(tripGroupDTO);
                Collections.sort(seriesTripGroups);
            } catch (IOException e) {
                throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
            }
        }
        return seriesTripGroups;
    }

    /**
     * PC端 - 旅行团首页三级分类
     *
     * @return
     */
    @Override
    public Result<TourPcHomeCatalogAndCmsDTO> getTripHomeCatalogAndBannerAndNotic() {
        TourPcHomeCatalogAndCmsDTO tourPcHomeCatalogAndCmsDTO = new TourPcHomeCatalogAndCmsDTO();
        // 三级分类
        Result<List<TripHomeCatalogDTO>> tripCatalogDTO = getTripCatalogDTO();
        if (tripCatalogDTO.isSuccess()) {
            List<TripHomeCatalogDTO> data = tripCatalogDTO.getData();
            if (CollectionUtil.isEmpty(data)) {
                data = new ArrayList<>();
            }
            tourPcHomeCatalogAndCmsDTO.setCatalogs(data);
        }

        // 公告
        Result<NoticePositionListDTO> trip_notice = noticePositionService.getNoticePositionByCode("TRIP_NOTICE");
        List<TourNoticeDTO> tourNoticeDTOS = new ArrayList<>();
        if (trip_notice.isSuccess()) {
            List<NoticeDTO> noticeDTOS = trip_notice.getData().getNoticeDTOS();
            if (CollectionUtil.isNotEmpty(noticeDTOS)) {
                for (NoticeDTO noticeDTO : noticeDTOS) {
                    TourNoticeDTO tourNoticeDTO = new TourNoticeDTO();
                    tourNoticeDTO.setTitle(noticeDTO.getTitle());
                    tourNoticeDTO.setContent(noticeDTO.getNoticeContent());
                    tourNoticeDTOS.add(tourNoticeDTO);
                }
            }
        }
        tourPcHomeCatalogAndCmsDTO.setMessage(tourNoticeDTOS);

        // banner
        Result<BannerPositionListDTO> trip_app_banner = bannerPositionService.getBannerPositionByCode("TRIP_PC_BANNER");
        List<TourBannerDTO> tourBannerDTOS = new ArrayList<>();
        if (trip_app_banner.isSuccess()) {
            List<BannerDTO> banners = trip_app_banner.getData().getBanners();
            if (CollectionUtil.isNotEmpty(banners)) {
                for (BannerDTO banner : banners) {
                    TourBannerDTO tourBannerDTO = new TourBannerDTO();
                    tourBannerDTO.setBannerUrl(banner.getBannerUrl());
                    if (ObjectUtils.isNotEmpty(banner.getLinkTypeDto())) {
                        tourBannerDTO.setType(banner.getLinkTypeDto().getLinkType());
                        tourBannerDTO.setLinkUrl(banner.getLinkTypeDto().getLinkUrl());
                    }
                    tourBannerDTOS.add(tourBannerDTO);
                }
            }
        }
        tourPcHomeCatalogAndCmsDTO.setBanner(tourBannerDTOS);
        return Result.success(tourPcHomeCatalogAndCmsDTO);
    }

    /**
     * 获取分类缓存
     *
     * @return
     */
    private Result<List<TripHomeCatalogDTO>> getTripCatalogDTO() {
        if (!hasKey(RedisConstants.TOUR_HOME_CATAGORY)) {
            // 加分布式锁
            RLock lock = redissonClient.getLock(RedisConstants.TOUR_HOME_CATAGORY + "getTripHomeCatalogDTO");
            lock.lock(30, TimeUnit.SECONDS);
            try {
                if (!hasKey(RedisConstants.TOUR_HOME_CATAGORY)) {
                    Boolean flag = tripHomeCatalogDTO();
                    if (!flag) {
                        return Result.failed(SystemError.TOUR_30013);
                    }
                }
            } finally {
                lock.unlock();
            }
        }

        String str = redisTemplate.opsForValue().get(RedisConstants.TOUR_HOME_CATAGORY);
        return Result.success(JSONArray.parseArray(str, TripHomeCatalogDTO.class));
    }

    /**
     * 旅行团首页目录三级分类缓存数据封装
     *
     * @return
     */
    @Override
    public Boolean tripHomeCatalogDTO() {
        // 菜单主数据
        List<CatalogMainDataDTO> catalogMainData = mainDataTourGroupService.getCatalogMainData();
        // 查询第三级分类是否有对应的行程
        List<Long> areaIds = catalogMainData.stream().map(item -> item.getAreaId()).collect(Collectors.toList());
        List<SeriesTrip> seriesTrips = baseMapper
                .selectList(Wrappers.<SeriesTrip>lambdaQuery().select(SeriesTrip::getDestinationId)
                        .eq(SeriesTrip::getStatus, TripStatusConstants.EFFECTIVE.getCode())
                        .in(SeriesTrip::getDestinationId, areaIds));

        // 如果所有第三级分类都没有行程
        if (CollectionUtil.isEmpty(seriesTrips)) return false;

        // 过滤有行程的第三级分类
        Set<Long> destinationIds = seriesTrips.stream().map(item -> item.getDestinationId()).collect(Collectors.toSet());
        List<CatalogMainDataDTO> newCatalogMainData = catalogMainData.stream()
                .filter(item -> destinationIds.contains(item.getAreaId())).collect(Collectors.toList());

        Set<String> tourGroupCode = catalogMainData.stream().map(item -> item.getTourGroupCode()).collect(Collectors.toSet());
        List<TripHomeCatalogDTO> lv1Data = new ArrayList<>();

        for (String code : tourGroupCode) {
            // 一级菜单
            List<CatalogMainDataDTO> lv1s = catalogMainData
                    .stream()
                    .filter(item -> item.getTourGroupCode().equals(code))
                    .collect(Collectors.toList());
            TripHomeCatalogDTO lv1 = new TripHomeCatalogDTO();
            lv1.setId(code);
            if (CollectionUtil.isNotEmpty(lv1s)) {
                lv1.setName(lv1s.get(0).getTourGroupName());
            }

            // 二级菜单
            List<TripHomeCatalogDTO> lv2s = new ArrayList<>();
            List<CatalogMainDataDTO> lv2Calogs = newCatalogMainData.stream().filter(item -> item.getTourGroupCode().equals(code)).collect(Collectors.toList());
            if (CollectionUtil.isEmpty(lv2Calogs)) {
                continue;
            }

            for (CatalogMainDataDTO region : lv2Calogs) {
                TripHomeCatalogDTO lv2 = new TripHomeCatalogDTO();
                lv2.setId(region.getRegionId());
                lv2.setName(region.getRegionName());

                // 三级菜单
                List<CatalogMainDataDTO> lv3s = lv2Calogs.stream()
                        .filter(item -> item.getRegionId().equals(region.getRegionId()))
                        .collect(Collectors.toList());

                List<TripHomeCatalogDTO> areas = lv3s.stream().map(item -> {
                    TripHomeCatalogDTO lv3 = new TripHomeCatalogDTO();
                    lv3.setId(region.getAreaId().toString());
                    lv3.setName(region.getAreaName());
                    return lv3;
                }).collect(Collectors.toList());

                lv2s.add(lv2);
                lv2.setCatalogs(areas);
            }
            lv1.setCatalogs(lv2s);
            lv1Data.add(lv1);
        }
        String string = JSON.toJSONString(lv1Data);
        redisTemplate.opsForValue().set(RedisConstants.TOUR_HOME_CATAGORY, string, 1, TimeUnit.DAYS);
        return true;
    }

    @Override
    public void deleteTripCache(Long id) {
        // 删除redis缓存
        List<SeriesTripChannelOnlineSelfPlatform> seriesTripChannelOnlineSelfPlatforms = tripChannelOnlineSelfPlatformMapper
                .selectList(Wrappers.<SeriesTripChannelOnlineSelfPlatform>lambdaQuery()
                        .select(SeriesTripChannelOnlineSelfPlatform::getChannelType)
                        .eq(SeriesTripChannelOnlineSelfPlatform::getIsDeleted, IsDeletedCode.NO.getCode()));
        Set<Long> channelIds = seriesTripChannelOnlineSelfPlatforms.stream().map(item -> item.getChannelType()).collect(Collectors.toSet());
        for (Long channelId : channelIds) {
            BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + channelId.toString() + ":");
            if (hashOps.hasKey(id.toString())) {
                hashOps.delete(id.toString());
            }
        }

        // 删除es缓存
        TripCache tripCache = new TripCache();
        tripCache.setId(id);
        seriesTripElasticSearchMapper.delete(tripCache);
    }

    @Override
    public List<H5TripGroupDTO> getH5HotGroupInfo(LocalHotSearchReq req) {
        List<TripGroup> hotGroupInfo = getHotGroupInfo(req);
        List<H5TripGroupDTO> tripsGroupDTOS = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(hotGroupInfo)) {
            for (TripGroup tripGroup : hotGroupInfo) {
                H5TripGroupDTO tripsGroup = EntityUtil.copy(tripGroup, H5TripGroupDTO.class);
                tripsGroup.setMoney(tripGroup.getBasePrice());
                tripsGroupDTOS.add(tripsGroup);
            }
        }
        return tripsGroupDTOS;
    }

    /**
     * 旅行团 检索
     *
     * @param param 检索条件
     * @return 检索结果
     */
    @Override
    public TripSearchResDTO getTripsByParams(TripSearchReq param) {
        TripSearchResDTO result;
        SearchRequest searchRequest = buildSearchRequset(param);
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
            result = buildPCSearchResult(response, param);
        } catch (IOException e) {
            throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
        }
        return result;
    }

    /**
     * PC端-旅行团详情
     *
     * @param id        行程id
     * @param channelId 渠道id
     * @param userId    用户id
     * @return
     */
    @Override
    public Result<TripDetailsResDTO> getPCTripDetails(Long id, Long channelId, Long userId) {
        TripDetailsReq tripDetailsReq = new TripDetailsReq();
        tripDetailsReq.setChannelId(channelId);
        tripDetailsReq.setId(id);
        TripDetailsResDTO tripDetails = getPcTripDetails(tripDetailsReq, id, userId);
        if (ObjectUtils.isNotEmpty(tripDetails)) {

            return Result.success(tripDetails);
        }

        // 如果缓存中不存在，去数据库查询
        RLock lock = redissonClient.getLock(RedisConstants.TOUR_SCHEDULE_DETAILS_LOCK + channelId + ":" + id);
        lock.lock(30, TimeUnit.SECONDS);
        try {
            BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + channelId + ":");
            if (!hashOps.hasKey(id.toString())) {
                boolean b = queryTripDetails(channelId, id);
                if (!b) {
                    Result.failed(SystemError.TRIP_21003);
                }
            }
        } finally {
            lock.unlock();
        }
        tripDetails = getPcTripDetails(tripDetailsReq, id, userId);
        return Result.success(tripDetails);
    }

    /**
     * H5&APP端-旅行团详情
     *
     * @param req
     * @return
     */
    @Override
    public Result<TripDetailsH5ResDTO> getH5TripDetails(TripDetailsReq req) {
        TripDetailsH5ResDTO tripDetails = getAppAndH5TripDetails(req);
        if (ObjectUtils.isNotEmpty(tripDetails)) {
            return Result.success(tripDetails);
        }

        // 如果缓存中不存在，去数据库查询，加分布式锁
        RLock lock = redissonClient.getLock(RedisConstants.TOUR_SCHEDULE_DETAILS_LOCK + req.getChannelId() + ":" + req.getId());
        lock.lock(30, TimeUnit.SECONDS);
        try {
            BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + req.getChannelId() + ":");
            if (!hashOps.hasKey(req.getId().toString())) {
                boolean b = queryTripDetails(req.getChannelId(), req.getId());
                if (!b) {
                    Result.failed(SystemError.TRIP_21003);
                }
            }
        } finally {
            lock.unlock();
        }
        tripDetails = getAppAndH5TripDetails(req);
        return Result.success(tripDetails);
    }

    /**
     * @param req
     * @return
     */
    private TripDetailsH5ResDTO getAppAndH5TripDetails(TripDetailsReq req) {
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + req.getChannelId().toString() + ":");
        if (hashOps.hasKey(req.getId().toString())) {
            String str = hashOps.get(req.getId().toString());
            TripDetailsCacheDTO cache = JSON.parseObject(str, TripDetailsCacheDTO.class);
            TripDetailsH5ResDTO h5Res = EntityUtil.copy(cache, TripDetailsH5ResDTO.class);
            h5Res.setSubTitle(cache.getSubtitle());
            h5Res.setPrice(cache.getBasePrice());
            h5Res.setTripCode(cache.getTripCode());
            // 行程特色
            h5Res.setHintList(cache.getTripFeaturesJson());
            // 行程主题
            h5Res.setTagList(cache.getTourThemeJson());
            // 每日行程
            h5Res.setScenicList(getDays(cache.getTripItineraryDays()));
            // 媒体资源
            h5Res.setMediaList(getMediaList(cache));
            // 行程有效团期数
            Integer scheduleNum = 0;
            LocalDate now = LocalDate.now();
            if (CollectionUtil.isNotEmpty(cache.getDateListDTOS())) {
                List<ScheduleInfoCacheDTO> dateList = cache.getDateListDTOS()
                        .stream()
                        .filter(item -> item.getDepartureDate().isEqual(now) || item.getDepartureDate().isAfter(now))
                        .collect(Collectors.toList());
                scheduleNum = CollectionUtil.isNotEmpty(dateList) ? dateList.size() : 0;
            }
            h5Res.setScheduleNum(scheduleNum);
            // 获取最近分社距离
            if (ObjectUtils.isNotEmpty(req.getLat()) && ObjectUtils.isNotEmpty(req.getLon())) {
                List<BranchOfficeListDTO> branchOfficeListDTOS = queryBranch(req.getLon(), req.getLat());
                if (CollectionUtil.isNotEmpty(branchOfficeListDTOS)) {
                    h5Res.setDistance(branchOfficeListDTOS.get(0).getMeter());
                }
            }

            // 添加到我的足迹
            addUserFootPoint(cache, req.getId(), req.getUserId());
            return h5Res;
        }
        return null;
    }

    /**
     * 获取分社位置
     *
     * @param lon
     * @param lat
     * @return
     */
    private List<BranchOfficeListDTO> queryBranch(Double lon, Double lat) {
        if (ObjectUtils.isNotEmpty(lon) && ObjectUtils.isNotEmpty(lat)) {
            // 获取分社列表
            BOQueryReq boQueryReq = new BOQueryReq();
            boQueryReq.setLon(lon);
            boQueryReq.setLat(lat);
            Result<List<BranchOfficeDTO>> query = baiDuApiService.query(boQueryReq);
            List<BranchOfficeDTO> data = query.getData();
            return EntityUtil.copyList(data, BranchOfficeListDTO.class);
        }
        return new ArrayList<>(0);
    }

    private List<TripDetailsMediaDTO> getMediaList(TripDetailsCacheDTO cache) {
        // 媒体资源
        List<TripDetailsMediaDTO> media = new ArrayList<>();
        List<String> videoUrl = cache.getVideoUrl();
        List<String> rotatePicturesJson = cache.getRotatePicturesJson();
        // 视频
        if (CollectionUtil.isNotEmpty(videoUrl)) {
            for (String url : videoUrl) {
                TripDetailsMediaDTO video = new TripDetailsMediaDTO();
                video.setType(1);
                video.setUrl(url);
                media.add(video);
            }
        }
        // 轮播图
        if (CollectionUtil.isNotEmpty(rotatePicturesJson)) {
            for (String image : rotatePicturesJson) {
                TripDetailsMediaDTO rotate = new TripDetailsMediaDTO();
                rotate.setType(2);
                rotate.setUrl(image);
                media.add(rotate);
            }
        }
        return media;
    }

    /**
     * 行程详情每日行程数据封装
     *
     * @param tripItineraryDays
     * @return
     */
    private List<TripScheduleItineraryDayResDTO> getDays(List<TripScheduleItineraryDayDTO> tripItineraryDays) {
        // 每日行程详情
        List<TripScheduleItineraryDayResDTO> days = new ArrayList<>();
        Collections.sort(tripItineraryDays);
        boolean isOpen = true;
        for (TripScheduleItineraryDayDTO itineraryDay : tripItineraryDays) {
            TripScheduleItineraryDayResDTO day = new TripScheduleItineraryDayResDTO();
            TripH5MessageDTO message = new TripH5MessageDTO();
            day.setNum(itineraryDay.getDay());
            day.setTitle(itineraryDay.getTitle());
            message.setRemark(itineraryDay.getDayRemark());
            day.setIsOpen(isOpen);
            if (isOpen) isOpen = false;

            // 交通信息
            List<TrafficJsonDTO> trafficJsons = itineraryDay.getTrafficJson();
            List<TrafficJsonDTO> trafficJsonDTOList = trafficJsons.stream().filter(item -> null != item.getId()).collect(Collectors.toList());
            TrafficH5ResDTO traffiRes = new TrafficH5ResDTO();
            List<TrafficDTO> traffics = new ArrayList<>();
            if (CollectionUtil.isNotEmpty(trafficJsonDTOList)) {
                for (TrafficJsonDTO trafficJson : trafficJsonDTOList) {
                    TrafficDTO traffic = new TrafficDTO();
                    traffic.setName(trafficJson.getName());
                    traffic.setFrequency(trafficJson.getFrequency());
                    traffics.add(traffic);
                }
                traffiRes.setRemark(itineraryDay.getTrafficRemark());
            }
            traffiRes.setList(traffics);
            message.setTraffic(traffiRes);
            // 景点
            List<ScenicJsonDTO> scenicJson = itineraryDay.getScenicJson();
            List<ScenicH5ResDTO> scenicResList = new ArrayList<>();
            if (CollectionUtil.isNotEmpty(scenicJson)) {
                for (ScenicJsonDTO scenicJsonDTO : scenicJson) {
                    ScenicH5ResDTO scenicRes = new ScenicH5ResDTO();
                    scenicRes.setBrief(scenicJsonDTO.getContent());
                    scenicRes.setName(scenicJsonDTO.getName());
                    scenicRes.setImage(scenicJsonDTO.getPicture());
                    scenicResList.add(scenicRes);
                }
            }
            if (CollectionUtil.isEmpty(scenicResList)) {
                ScenicH5ResDTO scenicRes = new ScenicH5ResDTO();
                scenicResList.add(scenicRes);
            }
            message.setLocation(scenicResList);

            // 用餐
            FoodH5DTO food = new FoodH5DTO();
            List<FoodJsonDTO> foodJson = itineraryDay.getFoodJson();
            List<FoodJsonDTO> foodJsonDTOS = foodJson.stream().filter(item -> StringUtils.isNotEmpty(item.getType())).collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(foodJsonDTOS)) {
                List<FoodListH5DTO> foodList = new ArrayList<>();
                List<String> foodPhotoJson = itineraryDay.getFoodPhotoJson();
                List<String> foodImage = foodPhotoJson.stream().filter(item -> StringUtils.isNotEmpty(item.trim())).collect(Collectors.toList());
                int i = 0;
                for (FoodJsonDTO foodJsonDTO : foodJsonDTOS) {
                    FoodListH5DTO foodListH5DTO = new FoodListH5DTO();
                    foodListH5DTO.setKey(foodJsonDTO.getType());
                    foodListH5DTO.setWord(foodJsonDTO.getName());
                    if (CollectionUtil.isNotEmpty(foodImage)) {
                        if (i < foodImage.size() && StringUtils.isNotEmpty(foodImage.get(i))) {
                            foodListH5DTO.setImage(foodImage.get(i));
                            i++;
                        }
                    }
                    foodList.add(foodListH5DTO);
                }
                food.setRemark(itineraryDay.getFoodRemark());
                food.setList(foodList);
                message.setFood(food);
            }

            // 住宿
            HotelH5ResDTO hotelRes = new HotelH5ResDTO();
            List<HotelJsonDTO> hotelJson = itineraryDay.getHotelJson();
            List<HotelH5DTO> hotel = new ArrayList<>();
            List<HotelJsonDTO> hotelJsonDTOS = hotelJson.stream().filter(item -> StringUtils.isNotEmpty(item.getName())).collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(hotelJsonDTOS)) {
                for (HotelJsonDTO hotelJsonDTO : hotelJsonDTOS) {
                    HotelH5DTO hotelH5DTO = new HotelH5DTO();
                    hotelH5DTO.setTitle(hotelJsonDTO.getName());
                    List<String> picture = hotelJsonDTO.getPicture();
                    if (CollectionUtil.isNotEmpty(picture)) {
                        hotelH5DTO.setImage(picture.get(0));
                    }
                    hotel.add(hotelH5DTO);
                }
                hotelRes.setList(hotel);
                hotelRes.setRemark(itineraryDay.getHotelRemark());
                message.setSleep(hotelRes);
            }
            day.setMessage(message);
            days.add(day);
        }
        return days;
    }

    /**
     * PC端 旅游团 行程详情
     *
     * @param req
     * @return
     */
    private TripDetailsResDTO getPcTripDetails(TripDetailsReq req, Long id, Long userId) {
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + req.getChannelId() + ":");
        if (hashOps.hasKey(req.getId().toString())) {
            String str = hashOps.get(req.getId().toString());
            TripDetailsCacheDTO cache = JSON.parseObject(str, TripDetailsCacheDTO.class);
            TripDetailsResDTO pcRes = EntityUtil.copy(cache, TripDetailsResDTO.class);
            pcRes.setSubTitle(cache.getSubtitle());
            pcRes.setPrice(cache.getBasePrice());
            pcRes.setHintList(cache.getTripFeaturesJson());
            pcRes.setTagList(cache.getTourThemeJson());
            pcRes.setScenicList(getDays(cache.getTripItineraryDays()));
            pcRes.setMediaList(getMediaList(cache));
            pcRes.setCover(cache.getCoverPhotoUrl());
            List<ScheduleInfoCacheDTO> dateListDTOS = pcRes.getDateListDTOS();
            List<ScheduleInfoCacheDTO> dateList = new ArrayList<>();
            if (CollectionUtil.isNotEmpty(dateListDTOS)) {
                LocalDate now = LocalDate.now();
                dateList = dateListDTOS.stream().filter(item -> item.getDepartureDate().isEqual(now)
                        || item.getDepartureDate().isAfter(now)).collect(Collectors.toList());

                if (CollectionUtil.isNotEmpty(dateList)) {
                    for (ScheduleInfoCacheDTO scheduleInfoCacheDTO : dateList) {
                        for (TripTouristTypeDTO touristType : scheduleInfoCacheDTO.getTouristTypes()) {
                            touristType.setAdjustFeeList(null);
                            touristType.setCostList(null);
                        }
                    }
                    Collections.sort(dateList);
                }
            }
            pcRes.setDateListDTOS(dateList);

            // 添加到足迹
            addUserFootPoint(cache, id, userId);
            return pcRes;
        }
        return null;

    }

    /**
     * 查询行程详情
     *
     * @param channelId
     * @param tripId
     */
    private boolean queryTripDetails(Long channelId, Long tripId) {
        LambdaQueryWrapper<SeriesTrip> queryWrapper = Wrappers.<SeriesTrip>lambdaQuery().eq(SeriesTrip::getIsDeleted, IsDeletedCode.NO.getCode());
        queryWrapper.eq(SeriesTrip::getStatus, ApprovalStatus.REVIEW_YES.getCode());
        queryWrapper.eq(SeriesTrip::getId, tripId);
        List<SeriesTrip> seriesTrips = baseMapper.selectList(queryWrapper);
        if (CollectionUtil.isEmpty(seriesTrips)) {
            return false;
        }

        // 行程团期
        List<SeriesTripSchedule> seriesTripSchedules = seriesTripScheduleMapper
                .selectList(Wrappers.<SeriesTripSchedule>lambdaQuery()
                        .eq(SeriesTripSchedule::getIsDeleted, IsDeletedCode.NO.getCode())
                        .between(SeriesTripSchedule::getScheduleStatus, 1, 2)
                        .in(SeriesTripSchedule::getTripId, tripId));
        Set<Long> scheduleIds = seriesTripSchedules.stream().filter(item -> item.getTripId().longValue() == tripId.longValue()).map(item -> item.getId()).collect(Collectors.toSet());
        if (CollectionUtil.isEmpty(scheduleIds)) {
            return false;
        }
        // 行程渠道线上自营加幅策略
        List<SeriesTripChannelOnlineSelfPlatform> channelOnlineSelfPlatforms = tripChannelOnlineSelfPlatformMapper
                .selectList(Wrappers.<SeriesTripChannelOnlineSelfPlatform>lambdaQuery()
                        .eq(SeriesTripChannelOnlineSelfPlatform::getIsDeleted, IsDeletedCode.NO.getCode())
                        .eq(SeriesTripChannelOnlineSelfPlatform::getTripId, tripId));

        // 行程默认成本分摊
        List<SeriesTripDefaultCost> tripDefaultCosts = seriesTripDefaultCostMapper.selectList(Wrappers.<SeriesTripDefaultCost>lambdaQuery()
                .eq(SeriesTripDefaultCost::getIsDeleted, IsDeletedCode.NO.getCode())
                .eq(SeriesTripDefaultCost::getTripId, tripId));

        // 行程每日行程
        List<SeriesTripScheduleItineraryDay> seriesTripScheduleItineraryDays = seriesTripScheduleItineraryDayMapper
                .selectList(Wrappers.<SeriesTripScheduleItineraryDay>lambdaQuery()
                        .eq(SeriesTripScheduleItineraryDay::getIsDeleted, IsDeletedCode.NO.getCode())
                        .eq(SeriesTripScheduleItineraryDay::getTripId, tripId));

        // 渠道所有有效团期加幅策略
        seriesTripSchedules.stream().map(item -> item.getId()).collect(Collectors.toList());
        List<SeriesTripScheduleChannelOnlineSelfPlatform> scheduleOnlineSelfPlatforms = scheduleChannelOnlineSelfPlatformMapper
                .selectList(Wrappers.<SeriesTripScheduleChannelOnlineSelfPlatform>lambdaQuery()
                        .eq(SeriesTripScheduleChannelOnlineSelfPlatform::getIsDeleted, IsDeletedCode.NO.getCode())
                        .eq(SeriesTripScheduleChannelOnlineSelfPlatform::getChannelType, channelId)
                        .in(SeriesTripScheduleChannelOnlineSelfPlatform::getScheduleId, scheduleIds));

        // 团期团队规则 - 最大可销售数量 - 用来计算剩余可售位数
        List<SeriesTripScheduleTeamRule> seriesTripScheduleTeamRules = seriesTripScheduleTeamRuleMapper
                .selectList(Wrappers.<SeriesTripScheduleTeamRule>lambdaQuery()
                        .select(SeriesTripScheduleTeamRule::getMaxPeople, SeriesTripScheduleTeamRule::getTripId, SeriesTripScheduleTeamRule::getScheduleId)
                        .eq(SeriesTripScheduleTeamRule::getTripId, tripId)
                        .in(SeriesTripScheduleTeamRule::getScheduleId, scheduleIds));

        // 团期成本默认分摊
        List<SeriesTripScheduleDefaultCost> seriesTripScheduleDefaultCosts = seriesTripScheduleDefaultCostMapper
                .selectList(Wrappers.<SeriesTripScheduleDefaultCost>lambdaQuery()
                        .eq(SeriesTripScheduleDefaultCost::getIsDeleted, IsDeletedCode.NO.getCode())
                        .eq(SeriesTripScheduleDefaultCost::getTripId, tripId)
                        .in(SeriesTripScheduleDefaultCost::getScheduleId, scheduleIds));

        // 团期成本自定义分摊
        List<SeriesTripScheduleCustomCost> seriesTripScheduleCustomCosts = seriesTripScheduleCustomCostMapper
                .selectList(Wrappers.<SeriesTripScheduleCustomCost>lambdaQuery()
                        .eq(SeriesTripScheduleCustomCost::getIsDeleted, IsDeletedCode.NO.getCode())
                        .eq(SeriesTripScheduleCustomCost::getTripId, tripId)
                        .in(SeriesTripScheduleCustomCost::getScheduleId, scheduleIds));

        List<SeriesTripScheduleCustomSalePrice> seriesTripScheduleCustomSalePrices = null;
        if (CollectionUtil.isNotEmpty(seriesTripScheduleCustomCosts)) {
            List<Long> customCostIds = seriesTripScheduleCustomCosts.stream().map(item -> item.getId()).collect(Collectors.toList());
            seriesTripScheduleCustomSalePrices = seriesTripScheduleCustomSalePriceMapper
                    .selectList(Wrappers.<SeriesTripScheduleCustomSalePrice>lambdaQuery()
                            .in(SeriesTripScheduleCustomSalePrice::getCostId, customCostIds));
        }

        // 团期默认代收费用
        Set<Long> scheduleDefaultCostIds = seriesTripScheduleDefaultCosts.stream().map(item -> item.getId()).collect(Collectors.toSet());
        List<SeriesTripScheduleDefaultCostCollection> seriesTripScheduleDefaultCostCollections = null;
        if (CollectionUtil.isNotEmpty(scheduleDefaultCostIds)) {
            seriesTripScheduleDefaultCostCollections = seriesTripScheduleDefaultCostCollectionMapper
                    .selectList(Wrappers.<SeriesTripScheduleDefaultCostCollection>lambdaQuery()
                            .or(w -> w.eq(SeriesTripScheduleDefaultCostCollection::getIsDeleted, IsDeletedCode.NO.getCode())
                                    .or().isNull(SeriesTripScheduleDefaultCostCollection::getIsDeleted))
                            .in(SeriesTripScheduleDefaultCostCollection::getCostId, scheduleDefaultCostIds)
                            .in(SeriesTripScheduleDefaultCostCollection::getScheduleId, scheduleIds));
        }


        // 团期自定义代收费用
        Set<Long> customCostIds = seriesTripScheduleCustomCosts.stream().map(item -> item.getId()).collect(Collectors.toSet());
        List<SeriesTripScheduleCustomCostCollection> seriesTripScheduleCustomCostCollections = null;
        if (CollectionUtil.isNotEmpty(customCostIds)) {
            seriesTripScheduleCustomCostCollections = seriesTripScheduleCustomCostCollectionMapper
                    .selectList(Wrappers.<SeriesTripScheduleCustomCostCollection>lambdaQuery()
                            .or(w -> w.eq(SeriesTripScheduleCustomCostCollection::getIsDeleted, IsDeletedCode.NO.getCode())
                                    .or().isNull(SeriesTripScheduleCustomCostCollection::getIsDeleted))
                            .in(SeriesTripScheduleCustomCostCollection::getCostId, customCostIds)
                            .in(SeriesTripScheduleCustomCostCollection::getScheduleId, scheduleIds));
        }


        // 团期默认旅客类型
        List<SeriesTripScheduleDefaultCostTouristType> seriesTripScheduleDefaultCostTouristTypes = seriesTripScheduleDefaultCostTouristTypeMapper
                .selectList(Wrappers.<SeriesTripScheduleDefaultCostTouristType>lambdaQuery()
                        .eq(SeriesTripScheduleDefaultCostTouristType::getTripId, tripId));

        // 团期自定义旅客类型
        List<SeriesTripScheduleCustomTouristType> seriesTripScheduleCustomTouristTypes = seriesTripScheduleCustomTouristTypeMapper
                .selectList(Wrappers.<SeriesTripScheduleCustomTouristType>lambdaQuery()
                        .eq(SeriesTripScheduleCustomTouristType::getTripId, tripId));

        // 团期旅客类型
        List<SeriesTripScheduleTouristType> seriesTripScheduleTouristTypes = seriesTripScheduleTouristTypeMapper.selectList(Wrappers.<SeriesTripScheduleTouristType>lambdaQuery()
                .eq(SeriesTripScheduleTouristType::getIsDeleted, IsDeletedCode.NO.getCode())
                .in(SeriesTripScheduleTouristType::getScheduleId, scheduleIds).orderByDesc(SeriesTripScheduleTouristType::getSort));

        // 团期自定义调整费用
        List<ScheduleCustomFeeAdjustAndTouristDTO> customFeeAdjustAndTouristDTOS = customFeeAdjustMapper
                .selectCustomFeeAdjustList(new ArrayList<>(scheduleIds), new HashSet<Long>(Arrays.asList(tripId)), Constants.SINGLE_ROOM_ID);

        // 团期默认调整费用
        List<SeriesTripScheduleDefaultFeeAdjust> seriesTripScheduleDefaultFeeAdjusts = defaultFeeAdjustMapper
                .selectList(Wrappers.<SeriesTripScheduleDefaultFeeAdjust>lambdaQuery()
                        .or(w -> w.eq(SeriesTripScheduleDefaultFeeAdjust::getIsDeleted, IsDeletedCode.NO.getCode())
                                .or().isNull(SeriesTripScheduleDefaultFeeAdjust::getIsDeleted))
                        .in(SeriesTripScheduleDefaultFeeAdjust::getScheduleId, scheduleIds));

        // 一级二级菜单主数据
        List<CatalogMainDataDTO> catalogMainData = mainDataTourGroupService.getCatalogMainData();

        Map<Long, String> detailsMap = buildRedisCache(channelId, seriesTrips, seriesTripSchedules,
                seriesTripScheduleItineraryDays, scheduleOnlineSelfPlatforms, seriesTripScheduleTeamRules,
                seriesTripScheduleDefaultCosts, seriesTripScheduleCustomCosts, seriesTripScheduleCustomSalePrices,
                tripDefaultCosts, channelOnlineSelfPlatforms, seriesTripScheduleDefaultCostCollections,
                seriesTripScheduleCustomCostCollections, seriesTripScheduleDefaultCostTouristTypes,
                seriesTripScheduleCustomTouristTypes, seriesTripScheduleTouristTypes, catalogMainData,
                customFeeAdjustAndTouristDTOS, seriesTripScheduleDefaultFeeAdjusts);
        if (detailsMap == null) {
            return false;
        }
        String str = detailsMap.get(channelId);
        List<TripDetailsCacheDTO> detailsCacheDTOS = JSON.parseArray(str, TripDetailsCacheDTO.class);
        // 如果数据库中也没有行程信息
        if (null == detailsCacheDTOS || detailsCacheDTOS.size() <= 0) {
            // 删除es行程缓存
            seriesTripElasticSearchMapper.delete(new TripCache().setId(tripId));
            return false;
        }

        // 添加到redis
        upRedis(detailsMap);
        return true;
    }

    /**
     * h5首页搜索区域旅游分组
     *
     * @return
     */
    @Override
    public Result<List<TripHomeCatalogDTO>> getTripHomeH5CatalogDTO() {
        return getTripCatalogDTO();
    }

    /**
     * H5旅行团检索
     *
     * @param param
     * @return
     */
    @Override
    public TripH5SearchResDTO getTripsByParamsFromH5(TripSearchReq param) {
        TripH5SearchResDTO result;
        SearchRequest searchRequest = buildSearchRequset(param);
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
            result = buildH5SearchResult(response, param);
        } catch (IOException e) {
            throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
        }
        return result;
    }

    /**
     * 更新缓存
     *
     * @param req
     * @return
     */
    @Override
    public Result updateTripCache(TripCacheUpdateReq req) {
        LocalDateTime now = LocalDateTime.now();
        // 旅行团 检索数据缓存
        Map<String, Map<Long, String>> cacheMap = buildCacheData(req);
        if (ObjectUtils.isNotEmpty(cacheMap)) {
            // 行程检索缓存
            upElasticSearch(cacheMap.get("es"));
            // 行程详情缓存
            upRedis(cacheMap.get("redis"));
        }

        // 标记更新缓存时间
        if (ObjectUtils.isNotEmpty(cacheMap) && ObjectUtils.isNotEmpty(req) && ObjectUtils.isNotEmpty(req.getStartTime())) {
            setUpdateTime(RedisConstants.TOUR_CACHE_UPDATE, now);
        }
        return Result.success();
    }

    /**
     * PC 旅行团首页 关联搜索 目的地模糊匹配
     *
     * @param key
     * @return
     */
    @Override
    public List<DestinationDTO> associationSearch(String key) {
        List<AreaDTO> areaDTOS = areaService.queryListEs(new AreaQueryReq().setName(key).setSize(20));
        List<DestinationDTO> result = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(areaDTOS)) {
            for (AreaDTO area : areaDTOS) {
                DestinationDTO destinationDTO = new DestinationDTO();
                if (ObjectUtils.isNotEmpty(area.getCityTraditional())) {
                    destinationDTO.setName(area.getCityTraditional());
                    destinationDTO.setId(area.getId());
                    result.add(destinationDTO);
                    continue;
                }
                if (ObjectUtils.isNotEmpty(area.getProvinceTraditional())) {
                    destinationDTO.setName(area.getProvinceTraditional());
                    destinationDTO.setId(area.getId());
                    result.add(destinationDTO);
                    continue;
                }
                if (ObjectUtils.isNotEmpty(area.getCountryTraditional())) {
                    destinationDTO.setName(area.getCountryTraditional());
                    destinationDTO.setId(area.getId());
                    result.add(destinationDTO);
                }
            }
            return result;
        }
        return null;
    }

    /**
     * 当季热卖
     *
     * @return
     */
    @Override
    public List<TripGroup> getHotGroupInfo(LocalHotSearchReq req) {
        // 构建检索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
        nestedBoolQuery.must(QueryBuilders.matchQuery("basePrice.channelId", req.getChannelId()));
        nestedBoolQuery.must(QueryBuilders.rangeQuery("basePrice.price").gt("0"));
        boolQuery.must(QueryBuilders.nestedQuery("basePrice", nestedBoolQuery, ScoreMode.None));

        boolQuery.should(QueryBuilders.termQuery("isHot", 1));
        if (StringUtils.isNotEmpty(req.getGourpCode())) {
            boolQuery.must(QueryBuilders.matchQuery("tourTypeCode", req.getGourpCode()));
        }
        if (ObjectUtils.isNotEmpty(req.getCityId())) {
            boolQuery.must(QueryBuilders.matchQuery("destinationId", req.getCityId()));
        }
        boolQuery.should(QueryBuilders.matchQuery("isTeam", 0));
        boolQuery.should(QueryBuilders.matchQuery("isReserve", 0));
        sourceBuilder.query(boolQuery);
        sourceBuilder.sort(new FieldSortBuilder("isHot").order(SortOrder.DESC));
        sourceBuilder.sort(new FieldSortBuilder("_score").order(SortOrder.DESC));
        Integer size = ObjectUtils.isEmpty(req.getSize()) ? ElasticSearchConstant.PAGE_SIZE : req.getSize();
        Integer form = ObjectUtils.isEmpty(req.getPage()) || req.getPage() <= 0 ? 0 : (req.getPage() - 1) * size;
        sourceBuilder.from(form);
        sourceBuilder.size(size);
        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.TRIP_INDEX).source(sourceBuilder);
        try {
            // 解析响应结果
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
            SearchHit[] hits = response.getHits().getHits();
            List<TripGroup> tripGroups = new ArrayList<>();
            for (SearchHit hit : hits) {
                String str = hit.getSourceAsString();
                TripCacheDTO tripCacheDTO = JSON.parseObject(str, TripCacheDTO.class);
                List<TripBasePriceDTO> basePrice = tripCacheDTO.getBasePrice();
                for (TripBasePriceDTO priceDTO : basePrice) {
                    if (priceDTO.getChannelId().equals(req.getChannelId())) {
                        TripGroup tripGroup = EntityUtil.copy(tripCacheDTO, TripGroup.class);
                        tripGroup.setBasePrice(priceDTO.getPrice());
                        tripGroups.add(tripGroup);
                    }
                }
            }
            return tripGroups;
        } catch (IOException e) {
            throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
        }
    }

    /**
     * APP&H5首页热卖分组与公告
     *
     * @return
     */
    @Override
    public TourHomeHotTypeAndMsgDTO tripHotTypeAndMsgInfo() {
        TourHomeHotTypeAndMsgDTO typeAndMsg = new TourHomeHotTypeAndMsgDTO();
        // 公告
        Result<NoticePositionListDTO> trip_notice = noticePositionService.getNoticePositionByCode("TRIP_NOTICE");
        List<TourNoticeDTO> tourNoticeDTOS = new ArrayList<>();
        log.info("旅行团APP首页公告查询结果------->{}", trip_notice);
        if (trip_notice.isSuccess()) {
            List<NoticeDTO> tripNotice = trip_notice.getData().getNoticeDTOS();
            if (tripNotice != null && tripNotice.size() > 0) {
                for (NoticeDTO notice : tripNotice) {
                    TourNoticeDTO tourNoticeDTO = new TourNoticeDTO();
                    tourNoticeDTO.setTitle(notice.getTitle());
                    tourNoticeDTO.setContent(notice.getNoticeContent());
                    tourNoticeDTOS.add(tourNoticeDTO);
                }
            }
        }
        typeAndMsg.setMessage(tourNoticeDTOS);

        // banner
        Result<BannerPositionListDTO> trip_app_banner = bannerPositionService.getBannerPositionByCode("TRIP_APP_BANNER");
        log.info("旅行团APP首页banner查询结果：{}", trip_app_banner);
        List<TourBannerDTO> tourBannerDTOS = new ArrayList<>();
        if (ObjectUtils.isNotEmpty(trip_app_banner)) {
            if (trip_app_banner.isSuccess()) {
                List<BannerDTO> banners = trip_app_banner.getData().getBanners();
                if (CollectionUtil.isNotEmpty(banners)) {
                    for (BannerDTO banner : banners) {
                        TourBannerDTO tourBannerDTO = new TourBannerDTO();
                        tourBannerDTO.setBannerUrl(banner.getBannerUrl());
                        if (ObjectUtils.isNotEmpty(banner.getLinkTypeDto())) {
                            tourBannerDTO.setType(banner.getLinkTypeDto().getLinkType());
                            tourBannerDTO.setLinkUrl(banner.getLinkTypeDto().getLinkUrl());
                        }
                        tourBannerDTOS.add(tourBannerDTO);
                    }
                }
            }
        }
        typeAndMsg.setBanner(tourBannerDTOS);

        // 热卖分组
        if (hasKey(RedisConstants.TOUR_HOME_CATAGORY)) {
            List<TripTypeDTO> tripTypeDTOS = gettripType();
            typeAndMsg.setTypeDTOS(tripTypeDTOS);
            return typeAndMsg;
        }

        tripHomeCatalogDTO();
        List<TripTypeDTO> tripTypeDTOS = gettripType();
        typeAndMsg.setTypeDTOS(tripTypeDTOS);
        return typeAndMsg;
    }

    private List<TripTypeDTO> gettripType() {
        String str = redisTemplate.opsForValue().get(RedisConstants.TOUR_HOME_CATAGORY);
        List<TripHomeCatalogDTO> tripHomeCatalogDTOS = JSONArray.parseArray(str, TripHomeCatalogDTO.class);
        Collections.sort(tripHomeCatalogDTOS);
        List<TripTypeDTO> types = new ArrayList<>();
        for (TripHomeCatalogDTO catalog : tripHomeCatalogDTOS) {
            TripTypeDTO tripTypeDTO = new TripTypeDTO();
            tripTypeDTO.setId(catalog.getId());
            tripTypeDTO.setTitle(catalog.getName());
            types.add(tripTypeDTO);
        }
        return types;
    }


    /**
     * 费用说明
     *
     * @param req
     * @return
     */
    @Override
    public Result<TripExpenseDescriptionDTO> expenseDescription(TripDetailsReq req) {
        TripDetailsCacheDTO tripDetailsCache = getTripDetailsRedisCache(req);
        return Result.success(tripDetailsCache == null ? null : EntityUtil.copy(tripDetailsCache, TripExpenseDescriptionDTO.class));
    }

    /**
     * 从redis获取行程详情缓存
     *
     * @param req
     * @return
     */
    private TripDetailsCacheDTO getTripDetailsRedisCache(TripDetailsReq req) {
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + req.getChannelId() + ":");
        if (hashOps.hasKey(req.getId().toString())) {
            String str = hashOps.get(req.getId().toString());
            return JSON.parseObject(str, TripDetailsCacheDTO.class);
        }
        return null;
    }

    /**
     * 报名须知
     *
     * @param req
     * @return
     */
    @Override
    public Result<String> signNotice(TripDetailsReq req) {
        TripDetailsCacheDTO tripDetailsCache = getTripDetailsRedisCache(req);
        return Result.success(tripDetailsCache == null ? "" : tripDetailsCache.getSignNotice());
    }

    /**
     * 备注
     *
     * @param req
     * @return
     */
    @Override
    public Result remark(TripDetailsReq req) {
        TripDetailsCacheDTO tripDetailsCache = getTripDetailsRedisCache(req);
        return Result.success(tripDetailsCache == null ? "" : tripDetailsCache.getRemark());
    }

    /**
     * 团期日期和价格
     *
     * @param req
     * @return
     */
    @Override
    public Result<List<ScheduleInfoCacheDTO>> dateAndPrice(TripDetailsDateReq req) {
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + req.getChannelId() + ":");
        if (hashOps.hasKey(req.getId().toString())) {
            String str = hashOps.get(req.getId().toString());
            TripDetailsCacheDTO tripDetailsCacheDTO = JSON.parseObject(str, TripDetailsCacheDTO.class);
            List<ScheduleInfoCacheDTO> dateListDTOS = tripDetailsCacheDTO.getDateListDTOS();
            for (ScheduleInfoCacheDTO dateListDTO : dateListDTOS) {
                for (TripTouristTypeDTO touristType : dateListDTO.getTouristTypes()) {
                    touristType.setCostList(null);
                    touristType.setAdjustFeeList(null);
                }
            }
            Collections.sort(dateListDTOS);
            return Result.success(dateListDTOS);
        }
        return Result.success(new ArrayList<>(0));
    }

    /**
     * 行程详情 - 相关推荐
     *
     * @param req
     * @return
     */
    @Override
    public List<TripsRecommendDTO> recommends(TripRecommendReq req) {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.should(QueryBuilders.termQuery("destinationId", req.getDestinationId()));
        boolQuery.should(QueryBuilders.termQuery("departurePlaceId", req.getDeparturePlaceId()));
        boolQuery.should(QueryBuilders.termQuery("tourTypeCode", req.getGroupCode()));
        boolQuery.should(QueryBuilders.termQuery("groupNatureId", req.getGroupNatureId()));
        boolQuery.mustNot(QueryBuilders.termQuery("id", req.getTripId()));
        searchSourceBuilder.query(boolQuery);
        searchSourceBuilder.from(req.getPage() <= 0 ? 0 : (req.getPage() - 1) * ElasticSearchConstant.PAGE_SIZE);
        searchSourceBuilder.size(ElasticSearchConstant.PAGE_SIZE);
        searchSourceBuilder.sort(new FieldSortBuilder("_score").order(SortOrder.DESC));
        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.TRIP_INDEX);
        searchRequest.source(searchSourceBuilder);

        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
            SearchHit[] hits = response.getHits().getHits();
            List<TripsRecommendDTO> recommendDTOS = new ArrayList<>();
            for (SearchHit hit : hits) {
                String str = hit.getSourceAsString();
                TripCacheDTO tripCacheDTO = JSON.parseObject(str, TripCacheDTO.class);
                for (TripBasePriceDTO basePriceDTO : tripCacheDTO.getBasePrice()) {
                    if (basePriceDTO.getChannelId().equals(req.getChannelId()) && !basePriceDTO.getPrice().equals(BigDecimal.ZERO)) {
                        TripsRecommendDTO recommendDTO = EntityUtil.copy(tripCacheDTO, TripsRecommendDTO.class);
                        recommendDTO.setBasePrice(basePriceDTO.getPrice());
                        recommendDTO.setUrl(tripCacheDTO.getCoverPhotoUrl());
                        recommendDTOS.add(recommendDTO);
                    }
                }
            }
            return recommendDTOS;
        } catch (IOException e) {
            throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
        }
    }

    /**
     * 查询分社列表
     *
     * @param req
     * @return
     */
    @Override
    public List<BranchOfficeListDTO> queryBranchList(BranchQueryReq req) {
        if (ObjectUtils.isNotEmpty(req)) {
            return queryBranch(req.getLon(), req.getLat());
        }
        return new ArrayList<>(0);
    }

    /**
     * 查询分社详情
     *
     * @param branchId
     * @return
     */
    @Override
    public BranchOfficeDetailsDTO queryBranchDetails(String branchId) {
        if (StringUtils.isNotEmpty(branchId)) {
            Result<BranchOfficeDTO> branchOfficeDTOResult = branchOfficeService.queryById(branchId);
            BranchOfficeDTO data = branchOfficeDTOResult.getData();
            return EntityUtil.copy(data, BranchOfficeDetailsDTO.class);
        }
        return null;
    }

    /**
     * 旅行团下单页 根据团号查询行程标题&副标题
     *
     * @param tripNumber 团号
     * @return
     */
    @Override
    public TripTitleDTO queryTripTitle(String tripNumber) {
        if (ObjectUtils.isEmpty(tripNumber)) {
            return null;
        }

        SearchSourceBuilder searchSource = new SearchSourceBuilder();
        searchSource.query(QueryBuilders.nestedQuery("schedules", QueryBuilders.matchQuery("schedules.tripNumber", tripNumber), ScoreMode.None));
        String[] includes = new String[]{"title", "subtitle", "businessType"};
        String[] excludes = new String[]{};
        searchSource.fetchSource(includes, excludes);
        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.TRIP_INDEX).source(searchSource);

        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
            SearchHit[] hits = response.getHits().getHits();
            TripTitleDTO tripTitleDTO = new TripTitleDTO();
            if (ObjectUtils.isNotEmpty(hits) && hits.length > 0) {
                for (SearchHit hit : hits) {
                    String source = hit.getSourceAsString();
                    TripCacheDTO tripCacheDTO = JSON.parseObject(source, TripCacheDTO.class);
                    tripTitleDTO.setTitle(tripCacheDTO.getTitle());
                    tripTitleDTO.setSubTitle(tripCacheDTO.getSubtitle());
                    tripTitleDTO.setBusinessType(tripCacheDTO.getBusinessType());
                    break;
                }
            }
            return tripTitleDTO;
        } catch (IOException e) {
            throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
        }
    }

    /**
     * 旅行团 - 详情页 - 收藏
     *
     * @param req
     * @return
     */
    @Override
    public Result favorites(FavoritesReq req, TokenUser user) {
        if (ObjectUtils.isEmpty(user)) {
            Result.failed(SystemError.USER_1000);
        }
        UserCollectionReq userCollectionReq = new UserCollectionReq();
        userCollectionReq.setUserId(user.getId());
        userCollectionReq.setType(req.getBusinessType());
        userCollectionReq.setDetailId(req.getTripId());
        return userCollectionService.add(userCollectionReq);
    }

    /**
     * 旅行团行程详情上架redis
     *
     * @param redisCacheMap
     */
    private void upRedis(Map<Long, String> redisCacheMap) {
        Set<Long> keys = redisCacheMap.keySet();
        for (Long key : keys) {
            BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + key + ":");
            String str = redisCacheMap.get(key);
            List<TripDetailsCacheDTO> tripDetailsCache = JSON.parseArray(str, TripDetailsCacheDTO.class);
            Map<String, String> upMap = new HashMap<>();
            for (TripDetailsCacheDTO detailsCacheDTO : tripDetailsCache) {
                String tripId = detailsCacheDTO.getTripId().toString();
                upMap.put(tripId, JSON.toJSONString(detailsCacheDTO));
            }
            hashOps.putAll(upMap);
            hashOps.expire(1, TimeUnit.DAYS);
        }
    }

    /**
     * 计算渠道加幅后的价格
     *
     * @param increaseType 加幅类型,1:加，2:减
     * @param amount       加幅金额
     * @param basePrice    加幅前金额
     * @return 加幅后金额
     */
    private BigDecimal channelIncrease(Integer increaseType, BigDecimal amount, BigDecimal basePrice) {
        if (ObjectUtils.isEmpty(increaseType) || ObjectUtils.isEmpty(amount)) {
            return basePrice.setScale(0, BigDecimal.ROUND_UP);
        }
        // 加幅类型:加
        if (increaseType.intValue() == PlusTypeCode.PLUS.getCode().intValue()) {
            return basePrice.add(amount).setScale(0, BigDecimal.ROUND_UP);
        }
        // 加幅类型:减
        if (increaseType.intValue() == PlusTypeCode.SUBTRACT.getCode().intValue()) {
            return basePrice.subtract(amount).setScale(0, BigDecimal.ROUND_UP);
        }
        return basePrice.setScale(0, BigDecimal.ROUND_UP);
    }

    /**
     * 构建行程检索条件
     *
     * @param param
     * @return
     */
    private SearchRequest buildSearchRequset(TripSearchReq param) {
        // 构建DSL语句
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 1、构建bool - query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
        NestedQueryBuilder basePrice = QueryBuilders.nestedQuery("basePrice", nestedBoolQuery, ScoreMode.None);
        nestedBoolQuery.must(QueryBuilders.matchQuery("basePrice.channelId", param.getChannelId()));

        // should-模糊匹配
        if (StringUtils.isNotEmpty(param.getKeyword())) {
            BoolQueryBuilder mustBool = QueryBuilders.boolQuery();
            mustBool.should(QueryBuilders.matchPhraseQuery("title", param.getKeyword()));
            mustBool.should(QueryBuilders.matchPhraseQuery("destination", param.getKeyword()));
            mustBool.should(QueryBuilders.matchPhraseQuery("subtitle", param.getKeyword()));
            mustBool.should(QueryBuilders.nestedQuery("scenicJson", QueryBuilders.termQuery("scenicJson.name", param.getKeyword()), ScoreMode.None));
            mustBool.should(QueryBuilders.nestedQuery("schedules", QueryBuilders.termQuery("schedules.tripNumber", param.getKeyword()), ScoreMode.None));
            boolQuery.must(mustBool);

            // 高亮
            HighlightBuilder title = new HighlightBuilder();
            title.field("title");
            title.preTags("<b style='color:red'>");
            title.postTags("</b>");
            sourceBuilder.highlighter(title);

            HighlightBuilder subtitle = new HighlightBuilder();
            subtitle.field("subtitle");
            subtitle.preTags("<b style='color:red'>");
            subtitle.postTags("</b>");
            sourceBuilder.highlighter(subtitle);
        }

        //  目的地Id
        if (ObjectUtils.isNotEmpty(param.getDestinationId()) && param.getDestinationId() != 0) {
            boolQuery.must(QueryBuilders.matchQuery("destinationId", param.getDestinationId()));
        }

        //  行程id
        if (ObjectUtils.isNotEmpty(param.getId()) && param.getId() != 0) {
            boolQuery.must(QueryBuilders.matchQuery("tripId", param.getId()));
        }

        // 旅游主题id
        if (ObjectUtils.isNotEmpty(param.getTourThemeId()) && param.getTourThemeId() != 0) {
            boolQuery.must(QueryBuilders.nestedQuery("tourThemeJson", QueryBuilders.matchQuery("tourThemeJson.id", param.getTourThemeId()), ScoreMode.None));
        }

        // 出游天数
        if (ObjectUtils.isNotEmpty(param.getTravelDays()) && param.getTravelDays() != 0) {
            if (param.getTravelDays() > 9) {
                boolQuery.must(QueryBuilders.rangeQuery("travelDays").gte(param.getTravelDays()));
            } else {
                boolQuery.must(QueryBuilders.termQuery("travelDays", param.getTravelDays()));
            }

        }

        // 出游时间
        if (ObjectUtils.isNotEmpty(param.getTripBeginDate()) || ObjectUtils.isNotEmpty(param.getTripEndDate())) {
            RangeQueryBuilder dayRangeQuery = QueryBuilders.rangeQuery("departureDate");
            if (ObjectUtils.isNotEmpty(param.getTripBeginDate())) {
                dayRangeQuery.gte(param.getTripBeginDate());
            }
            if (ObjectUtils.isNotEmpty(param.getTripEndDate())) {
                dayRangeQuery.lte(param.getTripEndDate());
            }
            boolQuery.must(dayRangeQuery);
        }

        // 价格区间
        if (ObjectUtils.isNotEmpty(param.getStartPrice()) || ObjectUtils.isNotEmpty(param.getEndPrice())) {
            RangeQueryBuilder priceRangeQuery = QueryBuilders.rangeQuery("basePrice.price");
            if (ObjectUtils.isNotEmpty(param.getStartPrice())) {
                priceRangeQuery.gte(param.getStartPrice());
            }
            if (ObjectUtils.isNotEmpty(param.getEndPrice())) {
                priceRangeQuery.lte(param.getEndPrice());
            }
            nestedBoolQuery.must(priceRangeQuery);
            NestedQueryBuilder priceRange = QueryBuilders.nestedQuery("basePrice", nestedBoolQuery, ScoreMode.None);
            boolQuery.must(priceRange);
        } else {
            RangeQueryBuilder priceRangeQuery = QueryBuilders.rangeQuery("basePrice.price");
            priceRangeQuery.gt(0);
            nestedBoolQuery.must(priceRangeQuery);
        }
        boolQuery.must(basePrice);
        sourceBuilder.query(boolQuery);

        // 排序 1:价格降序 2:价格升序 3:天数降序 4:天数升序
        Integer sort = param.getSort();
        FieldSortBuilder sortBuilder = null;
        if (sort == null || sort == 1 || sort == 2) {
            NestedSortBuilder nestedSortBuilder = new NestedSortBuilder("basePrice");
            sortBuilder = SortBuilders.fieldSort("basePrice.price").setNestedSort(nestedSortBuilder);
        } else {
            sortBuilder = SortBuilders.fieldSort("travelDays");
        }
        sortBuilder.order(sort == null || sort == 1 || sort == 3 ? SortOrder.DESC : SortOrder.ASC);
        sourceBuilder.sort(sortBuilder);

        // 分页
        sourceBuilder.from(ObjectUtils.isEmpty(param.getPageNum()) || param.getPageNum() <= 0 ? 0 : (param.getPageNum() - 1) * ElasticSearchConstant.PAGE_SIZE);
        sourceBuilder.size(ObjectUtils.isEmpty(param.getPageSize()) || param.getPageSize() <= 0 ? ElasticSearchConstant.PAGE_SIZE : param.getPageSize());
        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.TRIP_INDEX).source(sourceBuilder);

        return searchRequest;
    }

    /**
     * 旅行团检索页 目的地&旅游主题聚合
     */
    private void aggregation(Long channelId, String groupCode, TripSearchResDTO pcSearchRes, TripH5SearchResDTO h5SearchRes) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.nestedQuery("basePrice", QueryBuilders.matchQuery("basePrice.channelId", channelId), ScoreMode.None));
        if (StringUtils.isNotEmpty(groupCode) && !groupCode.equals("-1")) {
            boolQuery.must(QueryBuilders.matchQuery("tourTypeCode", groupCode));
        }
        sourceBuilder.query(boolQuery);
        String[] includes = new String[]{"destinationId", "destination"};
        String[] excludes = new String[]{};
        sourceBuilder.fetchSource(includes, excludes);
        sourceBuilder.size(10);

        // 旅游主题聚合
        NestedAggregationBuilder tourTheme_agg = AggregationBuilders.nested(ElasticSearchConstant.TOURTHEME_AGG, ElasticSearchConstant.TOURTHEME_PATH);
        TermsAggregationBuilder tourTheme_id_agg = AggregationBuilders.terms(ElasticSearchConstant.TOURTHEME_ID_AGG).field(ElasticSearchConstant.TOURTHEME_ID);
        tourTheme_id_agg.subAggregation(AggregationBuilders.terms(ElasticSearchConstant.TOURTHEME_NAME_AGG).field(ElasticSearchConstant.TOURTHEME_NAME));
        tourTheme_agg.subAggregation(tourTheme_id_agg);
        sourceBuilder.aggregation(tourTheme_agg);

        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.TRIP_INDEX).source(sourceBuilder);
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
            if (ObjectUtils.isNotEmpty(pcSearchRes)) {
                // 解析目的地
                SearchHit[] hits = response.getHits().getHits();
                Set<TripDestination> tripDestinations = new HashSet<>();
                for (SearchHit hit : hits) {
                    String source = hit.getSourceAsString();
                    TripDestination tripDestination = JSON.parseObject(source, TripDestination.class);
                    tripDestinations.add(tripDestination);
                }
                pcSearchRes.setTripDestinations(new ArrayList<>(tripDestinations));
            }

            // 旅游主题聚合分析
            Aggregations aggregations = response.getAggregations();
            if (null != aggregations) {
                ParsedNested tourThemeAgg = aggregations.get(ElasticSearchConstant.TOURTHEME_AGG);
                ParsedLongTerms tourThemeId = tourThemeAgg.getAggregations().get(ElasticSearchConstant.TOURTHEME_ID_AGG);
                if (tourTheme_agg != null && tourThemeId.getBuckets().size() > 0) {
                    List<TourTheme> tourThemes = new ArrayList<>();
                    // 遍历旅游主题id聚合
                    for (Terms.Bucket bucket : tourThemeId.getBuckets()) {
                        TourTheme tourTheme = new TourTheme();
                        tourTheme.setId(bucket.getKeyAsNumber().longValue());
                        // 旅游主题id聚合 -> 旅游主题名称子聚合
                        ParsedStringTerms aggregation = bucket.getAggregations().get(ElasticSearchConstant.TOURTHEME_NAME_AGG);
                        tourTheme.setName(aggregation.getBuckets().get(0).getKeyAsString());
                        tourThemes.add(tourTheme);
                    }
                    if (ObjectUtils.isNotEmpty(h5SearchRes)) {
                        h5SearchRes.setTourThemes(tourThemes);
                    }
                    if (ObjectUtils.isNotEmpty(pcSearchRes)) {
                        pcSearchRes.setTourThemes(tourThemes);
                    }
                }
            }
        } catch (IOException e) {
            throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
        }
    }

    /**
     * 构建PC检索结果数据
     *
     * @param response 检索结果
     * @return 检索结果分析
     */
    private TripSearchResDTO buildPCSearchResult(SearchResponse response, TripSearchReq param) {
        TripSearchResDTO result = new TripSearchResDTO();
        // 检索到的命中记录
        SearchHits hits = response.getHits();

        // 提取检索到的记录
        SearchHit[] searchHits = hits.getHits();
        if (ObjectUtils.isNotEmpty(searchHits)) {
            // 行程纪记录列表
            List<Trip> tripList = new ArrayList<>();
            for (SearchHit hit : searchHits) {
                String source = hit.getSourceAsString();
                // 将命中记录反序列化成行程对象
                TripCacheDTO tripCacheDTO = JSON.parseObject(source, TripCacheDTO.class);
                List<TripBasePriceDTO> basePrice = tripCacheDTO.getBasePrice();
                for (TripBasePriceDTO basePriceDTO : basePrice) {
                    if (basePriceDTO.getChannelId().longValue() == param.getChannelId().longValue()
                            && basePriceDTO.getPrice().compareTo(BigDecimal.ZERO) == 1) {
                        Trip trip = EntityUtil.copy(tripCacheDTO, Trip.class);
                        trip.setBasePrice(basePriceDTO.getPrice());
                        tripList.add(trip);
                        // 如果用户有输入检索条件，将检索条件置为高亮
                        if (StringUtils.isNotEmpty(param.getKeyword())) {
                            HighlightField title = hit.getHighlightFields().get("title");
                            if (ObjectUtils.isNotEmpty(title)) {
                                Text[] fragments = title.getFragments();
                                StringBuilder builder = new StringBuilder();
                                for (Text fragment : fragments) {
                                    builder.append(fragment.toString());
                                }
                                trip.setTitle(builder.toString());
                            }
                        }
                    }
                }
            }
            // 把行程列表加入结果集
            result.setTrips(tripList);
        }
        // 设置同分组目的地与旅游主题
        aggregation(param.getChannelId(), param.getTourTypeCode(), result, null);
        // 命中的记录总数
        long value = hits.getTotalHits().value;
        if (value > 0) {
            // 计算总页数
            int totalPages = (int) (value % ElasticSearchConstant.PAGE_SIZE == 0 ? value / ElasticSearchConstant.PAGE_SIZE : ((value / ElasticSearchConstant.PAGE_SIZE) + 1));
            result.setTotalPages(totalPages);
            // 页码
            result.setPageNum(param.getPageNum() == null ? 1 : param.getPageNum());
            return result;
        }
        return null;
    }

    /**
     * 构建H5检索结果数据
     *
     * @param response 检索结果
     * @return 检索结果分析
     */
    private TripH5SearchResDTO buildH5SearchResult(SearchResponse response, TripSearchReq param) {
        TripH5SearchResDTO result = new TripH5SearchResDTO();
        // 检索到的命中记录
        SearchHits hits = response.getHits();

        // 提取检索到的记录
        SearchHit[] searchHits = hits.getHits();
        if (searchHits != null && searchHits.length > 0) {
            // 行程记录列表
            List<Trip> tripList = new ArrayList<>();
            for (SearchHit hit : searchHits) {
                String source = hit.getSourceAsString();
                TripCacheDTO tripCacheDTO = JSON.parseObject(source, TripCacheDTO.class);
                List<TripBasePriceDTO> basePrice = tripCacheDTO.getBasePrice();
                for (TripBasePriceDTO basePriceDTO : basePrice) {
                    if (basePriceDTO.getChannelId().equals(param.getChannelId()) && !basePriceDTO.getPrice().equals(BigDecimal.ZERO)) {
                        Trip trip = EntityUtil.copy(tripCacheDTO, Trip.class);
                        Set<String> tourTheme = tripCacheDTO.getTourThemeJson().stream().map(item -> item.getName()).collect(Collectors.toSet());
                        trip.setTripFeaturesJson(new ArrayList<>(tourTheme));
                        trip.setBasePrice(basePriceDTO.getPrice());
                        // 如果用户有输入检索条件，将检索条件置为高亮
                        if (StringUtils.isNotEmpty(param.getKeyword())) {
                            HighlightField title = hit.getHighlightFields().get("title");
                            if (title != null) {
                                Text[] fragments = title.getFragments();
                                StringBuilder builder = new StringBuilder();
                                for (Text fragment : fragments) {
                                    builder.append(fragment.toString());
                                }
                                trip.setTitle(builder.toString());
                            }
                            HighlightField subtitle = hit.getHighlightFields().get("subtitle");
                            if (title != null) {
                                Text[] fragments = subtitle.getFragments();
                                StringBuilder builder = new StringBuilder();
                                for (Text fragment : fragments) {
                                    builder.append(fragment.toString());
                                }
                                trip.setSubtitle(builder.toString());
                            }
                        }
                        // 把行程对象加入对应列表
                        tripList.add(trip);
                    }
                }
            }
            result.setTrips(tripList);
        }
        aggregation(param.getChannelId(), param.getTourTypeCode(), null, result);
        return result;
    }

    /**
     * 上架到es
     *
     * @param esCacheMap 上架的数据
     * @return
     */
    private Result upElasticSearch(Map<Long, String> esCacheMap) {
        Set<Long> keys = esCacheMap.keySet();
        for (Long key : keys) {
            List<TripCache> tripCaches = JSON.parseArray(esCacheMap.get(key), TripCache.class);
            seriesTripElasticSearchMapper.saveAll(tripCaches);
        }
        return Result.success();
    }

    /**
     * 标记缓存更新时间
     *
     * @param key
     * @param now
     */
    private void setUpdateTime(String key, LocalDateTime now) {
        redisTemplate.opsForValue().set(key, JSON.toJSONString(now));
    }

    /**
     * 从redis获取数据的上次更新时间
     *
     * @return
     */
    private LocalDateTime getUpdateTime() {
        boolean b = hasKey(RedisConstants.TOUR_CACHE_UPDATE);
        if (b) {
            String str = redisTemplate.opsForValue().get(RedisConstants.TOUR_CACHE_UPDATE);
            return JSON.parseObject(str, LocalDateTime.class);
        }
        return null;
    }

    /**
     * 获取行程最近7天销量
     *
     * @param ids
     * @return
     */
    private List<SeriesOrderDTO> getTripSaleCount(List<Long> ids) {
        return seriesOrderService.selectList(ids);
    }

    /**
     * redis中是否存在key
     *
     * @param key
     * @return
     */
    private boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    //增加足迹
    private void addUserFootPoint(TripDetailsCacheDTO details, Long detailId, Long userId) {
        try {
            if (userId != null) {
                UserFootPointReq req = new UserFootPointReq();
                req.setUserId(userId);
                req.setType(SystemBusinessType.OUTBOUND_TRAVEL.getCode());
                req.setTitle(details.getTitle());
                req.setContent(details.getSubtitle());
                req.setPrice(details.getBasePrice());
                req.setCurrencyId(Currency.HKD.getCode());
                req.setCurrency(Currency.HKD.getCurrency());
                List<String> rotatePicturesJson = details.getRotatePicturesJson();
                if (CollectionUtil.isNotEmpty(rotatePicturesJson)) {
                    req.setCoverUrl(rotatePicturesJson.get(0));
                }
                req.setDetailId(detailId);
                userFootPointService.add(req);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 缓存淘汰
     */
    @Override
    public Result cacheWeedOut() {
        // 有效线上渠道列表
        List<SeriesTripChannelOnlineSelfPlatform> seriesTripChannelOnlineSelfPlatforms = tripChannelOnlineSelfPlatformMapper.selectList(Wrappers.<SeriesTripChannelOnlineSelfPlatform>lambdaQuery()
                .eq(SeriesTripChannelOnlineSelfPlatform::getIsDeleted, IsDeletedCode.NO.getCode())
                .gt(SeriesTripChannelOnlineSelfPlatform::getAmount, 0)
                .isNotNull(SeriesTripChannelOnlineSelfPlatform::getTripId)
                .isNotNull(SeriesTripChannelOnlineSelfPlatform::getChannelType));
        Set<Long> channelIds = seriesTripChannelOnlineSelfPlatforms.stream().map(item -> item.getChannelType()).collect(Collectors.toSet());
        Set<Long> tripIds = new HashSet<>();
        for (Long channelId : channelIds) {
            BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstants.TOUR_SCHEDULE_DETAILS + channelId + ":");
            List<String> values = hashOps.values();
            for (String value : values) {
                TripDetailsCacheDTO detailsCacheDTO = JSON.parseObject(value, TripDetailsCacheDTO.class);
                tripIds.add(detailsCacheDTO.getTripId());
            }
        }
        TripCacheUpdateReq tripCacheUpdateReq = new TripCacheUpdateReq();
        tripCacheUpdateReq.setIds(new ArrayList<>(tripIds));
        return updateTripCache(tripCacheUpdateReq);
    }

}